Skip to content

AI: add InterlinedApiClient.Ai.cs service + artifact models #9

Description

@Adron

Add the service layer for /api/ai/*. Everything after this is UI.

Endpoints

  • GET /api/ai/status → {subscriber, providers[], defaultModels{}, quota{usedToday,dailyLimit,remaining}}
  • POST /api/ai/suggest → {ok, feature, artifact, usage{inputTokens,outputTokens,model}, quota{}}
  • POST /api/ai/generate → {ok, feature, created{...}, quota{}}

Request shapes

/suggest: {feature, input, context?, model?, maxOutputTokens?}
/generate: {feature, artifact, provider?, model?, scheduleImmediately?, crossPost?}

Artifact kinds by feature

feature suggest artifact kind generate created
powered_template list {listId}
powered_document document {documentId}
message_series message_series {listId} or {scheduledMessageIds,firstScheduledAt,lastScheduledAt} with scheduleImmediately:true
article_series doc_series {folderId, documentIds[]}
writing_assist message | thread | tags not persistable → 422 invalid_input

Error codes to model as first-class states

401 unauthorized, 403 subscription_required, 409 no_provider_configured, 422 invalid_input, 422 invalid_ai_output, 429 quota_exceeded, 429 rate_limited (honor Retry-After), 500/502 provider_error.

InterlinedApiException should carry the machine-readable code so the UI can branch on it instead of string-matching messages.

Acceptance criteria

  • Services/InterlinedApiClient.Ai.cs with GetAiStatusAsync, AiSuggestAsync, AiGenerateAsync.
  • Models/AiStatus.cs, Models/AiArtifact.cs (+ per-kind payloads), Models/AiQuota.cs, Models/AiUsage.cs.
  • The code field from error bodies is surfaced on InterlinedApiException.
  • GET /api/ai/status verified live against the test account.

Activity

  1. added
    parityWeb/API feature-parity work
    P0Must have for a credible parity claim
    on Sep 15, 2026
  2. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    Implemented in #137 — work continues in the PR from here.

    Three things differed from this issue's sketch, and they matter for #10–#15. First, quota is not one shape: /status returns {usedToday, dailyLimit, remaining} but /suggest returns only {usedToday, dailyLimit} — no remaining — so AiQuota.Remaining is nullable and UI should bind RemainingOrComputed. Second, AI is Anthropic-only app-wide with no per-user provider keys (removed server-side 2026-09-05): the provider field on /generate is audit-ledger-only and the openai/gemini entries in defaultModels are unreachable, so there's no provider picker to build. Third, the code field is surfaced on a new AiApiException rather than on InterlinedApiException — the latter is sealed and is being rewritten in another open PR; folding the two together once that lands is noted as a follow-up in the code.

    Also worth knowing: input-validation 422s cost no quota (verified — usedToday stayed 0 across two), but any call that reaches the model does, including one that fails, so the client pre-flights word caps / the 10-word series gate / missing powered_document source refs / non-persistable artifacts locally and never auto-retries. POST /api/ai/generate was deliberately never called (it writes to the shared test account), so its envelope is contract-transcribed and flagged unverified in the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must have for a credible parity claimarea:aiArea: aiparityWeb/API feature-parity work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions