Skip to content
API Reference

Platform API Reference

HTTP API for the content platform. Discovery endpoints are public. Customer-facing state-changing endpoints require a valid session and are same-origin protected; the billing webhook is instead authenticated by its payment-provider signature and is not callable by customers. This reference is generated from the live OpenAPI document, available at /api/openapi.json. Version 1.0.0.

Operations

Public infrastructure endpoints (health/liveness probes).

get/api/health

Liveness and readiness probe

Returns process uptime, server time, presence-only environment readiness booleans (never any secret values) and a bounded database reachability check. 200 when ready, 503 when not configured or the database did not answer. Not cached.

Authentication: None. Public endpoint.

Responses

  • 200Ready: configured and the database answered.
  • 503Degraded: required env missing or the database did not answer in time.

Discovery

Public, unauthenticated metadata endpoints for crawlers and integrations.

get/llms.txt

Machine-readable product description

Returns the llms.txt product overview (capabilities, data handling, plans, discovery links) as plain text for AI crawlers. Cacheable.

Authentication: None. Public endpoint.

Responses

  • 200Plain-text product description.

get/agents.json

Agent preset catalog

Returns the agent preset catalog (preset and agent shape: id, name, expertise, tier, dependencies). Discovery metadata only; internal prompt text is not disclosed. Cacheable.

Authentication: None. Public endpoint.

Responses

  • 200Agent preset catalog.

get/api/openapi.json

This OpenAPI document

Returns this OpenAPI 3.1 document describing the platform API. Cacheable.

Authentication: None. Public endpoint.

Responses

  • 200OpenAPI 3.1 document.

Ingestion

Session-authenticated document ingestion.

post/api/ingest

Ingest a reference document

Accepts a reference document for the authenticated caller in one of two modes: a multipart file upload (DOCX, TXT, or MD; parsed in-process) or a JSON body containing an already client-parsed document (PDF text). Each successful ingest is written to the append-only audit log. Returns the canonical parsed document.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Request body (application/json)

FieldTypeRequiredDescription
parsedobjectRequiredCanonical parsed-document contract. No PHI in the default configuration.
filenamestringOptionalOptional original filename for the audit record.

Request body (multipart/form-data)

FieldTypeRequiredDescription
filestring (binary)RequiredDOCX, TXT, or MD file (max 10 MB). PDF must be posted as JSON instead.

Responses

  • 200Document ingested. Returns the canonical parsed document.
  • 400Invalid JSON body, missing file field, or unexpected content type.
  • 401No valid session.
  • 403Request failed the same-origin check.
  • 413Uploaded file exceeds the 10 MB limit.
  • 415Unsupported format, or a PDF was uploaded via multipart (post it as JSON instead).
  • 422Parsed-document body failed schema validation (invalid_request, with issues), or the file could not be parsed (unparseable_document, with a reference).
  • 429Rate limit exceeded (rate_limited, per-user or per-tenant). Includes a Retry-After header.
  • 500Internal parse contract violation.

Compliance

Session-authenticated, admin-gated organization data export.

post/api/compliance/export

Export organization data (right of access)

Returns a JSON snapshot of the caller's organization (members, plan/usage summary, productions, audit history) as a downloadable attachment. Admin/owner only; tenant-scoped; rate-limited; the export itself is recorded in the audit log. No PHI in the default configuration. POST (not GET) because it writes one audit row.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Responses

  • 200A JSON data export (Content-Disposition: attachment).
  • 401No valid session.
  • 403No active membership, insufficient role (admin/owner required), or failed same-origin check.
  • 429Rate limit exceeded (rate_limited). Includes a Retry-After header.
  • 500Export could not be generated (opaque reference returned).

Chat

Session-authenticated chat answered by the multi-agent system, streamed as NDJSON.

post/api/chat

Ask the multi-agent system (streamed)

Answers the conversation with the chosen model and streams the answer as newline-delimited JSON events (see ChatStreamEvent). Session-authenticated, same-origin (CSRF) enforced, rate-limited per user and per tenant, one request in flight per user, and admitted by the spend gate (plan, Free daily cap, monthly allowance) before any model call; an unreadable plan or meter refuses with 503 rather than guessing. Checks run in this order: session (401), same-origin (403), per-user rate limit (429), JSON (400), schema (422), item risk (403), protected health information (422), model (400), active membership (403), per-tenant rate limit (429), in flight (429), spend gate (403/429/402/503), then the team and compare gates. Once the stream opens the status is 200; a later failure arrives as an error event. Aborting the request aborts the upstream model call; aborted calls are still metered.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Request body (application/json)

FieldTypeRequiredDescription
modelIdstringRequiredModel id from the governed registry. Unknown ids are refused 400; models above the caller's plan 403.
messagesarray of objectRequiredConversation so far, oldest first. Ignored when itemRisk is present (see itemRisk).
webSearchbooleanOptionalRequest native web search; honored only when the chosen model advertises the capability.
multiAgentbooleanOptionalReview mode: draft, critique and refine with the same model.
teamobjectOptionalTeam mode. agentModels is a proposal only: every entry is re-checked against the caller's plan before the stream opens.
compareobjectOptionalCompare mode. Duplicate ids are collapsed before the plan and daily-limit checks; every model must be in the caller's plan or the whole request is refused.
itemRiskobjectOptionalAgent-Runner item execution only. When present, the server re-classifies the item and may refuse it (403), and the prompt sent to the model is rendered from kind/title/body alone - messages is not sent.

Responses

  • 200The answer stream: application/x-ndjson; charset=utf-8, one ChatStreamEvent per line, never cached.

    application/x-ndjson, one of:

    • phase: channel, label (optional), modelId (optional) - A phase (or agent, or compared model) starts on a channel.
    • delta: channel, text - A chunk of model text for a channel.
    • step_error: channel, message - One team agent or one compared model failed; the run continues. The message is a fixed, safe string (never provider exception text).
    • failover: channel, fromLabel, toLabel, modelId - The chosen model's provider failed before answering and another model in the caller's plan is answering instead. Labels only, never the error text.
    • done - Terminal: the run completed.
    • aborted - Terminal: the client aborted the request (the aborted calls are still metered).
    • error: message, reference, retryAfterSec (optional) - Terminal: the run failed after the stream opened. The HTTP status is still 200; this event carries the failure.
  • 400Invalid JSON body; unknown or unavailable model (also for a team agent or a compared model); team and multiAgent together; compare with team or multiAgent; unknown team.
  • 401No valid session.
  • 402This month's included usage is used up and pay-as-you-go is not enabled (monthly allowance gate).
  • 403Failed same-origin check; the itemRisk item was refused by the server-side risk gate; no active tenant membership; the model (or a team agent's or a compared model) is not in the caller's plan; no model in the caller's plan can run the team.
  • 422Body failed schema validation (issues lists the Zod messages); or a user turn contains a Social Security number, medical record number or date of birth (phi_detected: the detail names the kinds, never the values; nothing is sent to a model or metered).
  • 429RFC 9457 problem. code rate_limited (per-user or per-tenant burst limit; Retry-After is the wait), request_in_flight (the caller's previous message is still running; no Retry-After), daily_limit_reached (Free daily cap; Retry-After is the time to 00:00 UTC) or compare_exceeds_daily_limit (a compare needs more of today's messages than are left; Retry-After is the time to 00:00 UTC).
  • 503The plan, the daily count or the monthly spend could not be read, so nothing was run.

Design

Session-authenticated asynchronous video generation (submit, poll, then stream the finished video).

get/api/design/video

Poll a video-generation job

Returns the current state of a job the caller submitted. Session-authenticated; read-only, so no same-origin check and no rate limit (a poller fires every few seconds by design). A job id the caller did not submit, or one older than the 24-hour ownership window, is answered 404 so its existence is never confirmed. The poll goes to the provider the job was submitted to. A failed job reports a fixed, general message; a completed job's createdAt is when it was submitted, and its video.url is the platform's content address for the job (GET /api/design/video/content). Job ownership is not shared across server instances: poll soon after submitting, and treat a 404 for a job you did submit as the job being no longer pollable.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Parameters

NameInTypeRequiredDescription
jobIdquerystringRequiredThe jobId from the submit response.
providerquerystringOptionalOptional: the provider from the submit response. Omitted, the job's own provider is used. A provider that is not registered is answered 400; one other than the job's own provider is answered 404, as for an unknown job.

Responses

  • 200Current job state.
  • 400jobId is missing, empty or longer than 256 characters, or provider is not a registered provider.
  • 401No valid session.
  • 404No job with this id was submitted by the caller (or its ownership record expired), or provider is not the provider the job was submitted to.
  • 422A provider moderation refusal while polling. The handler maps it, but the current provider adapter never raises one on a poll.
  • 500Unexpected failure checking the job (reference returned).
  • 502The video provider failed or returned an unusable response.
  • 503Video generation is not configured.

post/api/design/video

Submit a video-generation job

Submits an asynchronous video render and returns a handle to poll with GET. Session-authenticated, same-origin (CSRF) enforced, active tenant membership required, rate-limited per user and per tenant, one submit in flight per user, and subject to the Free daily video cap (fails closed: an unreadable count is a 503). The prompt is screened by the safety pre-filter before it can reach a provider. One usage row is recorded per accepted submit. Checks run in this order: session (401), same-origin (403), per-user rate limit (429), membership (403), per-tenant rate limit (429), body size (413), JSON (400), schema (422), safety filter (422), in flight (429), daily cap (429/503), provider. A body that fails the size check, JSON, schema or the safety filter is refused before the in-flight check and the daily-cap read.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Request body (application/json)

FieldTypeRequiredDescription
promptstringRequiredLength is checked after trimming whitespace.
aspectenum: 1:1 | 16:9 | 9:16 | 4:3 | 3:4Optional
durationSecondsintegerOptionalRequested clip length in whole seconds, 1-30. A length outside that range is refused 422 rather than shortened. The provider renders the nearest length it supports.
modelstringOptionalModel id within the provider (checked after trimming). A model the provider does not declare is refused 400.

Responses

  • 202Job accepted.
  • 400The body is not valid UTF-8 JSON, or names a model the provider does not declare.
  • 401No valid session.
  • 403Failed same-origin check, or no active tenant membership.
  • 404Video generation is not available: it is out of launch and its server-side switch is off. Nothing is consumed.
  • 413The body (by its declared Content-Length, or by the bytes actually read) exceeds 13744 bytes, the largest body the schema can accept.
  • 422Body failed schema validation (issues lists the Zod messages), or the prompt was refused by the safety filter (error carries the user-facing reason).
  • 429RFC 9457 problem. code rate_limited (per-user or per-tenant; Retry-After is the wait), request_in_flight (the caller's previous submit is still running; no Retry-After) or daily_limit_reached (Free daily video cap; Retry-After is the time to 00:00 UTC).
  • 500Unexpected failure starting the job (reference returned).
  • 502The video provider failed or returned an unusable response.
  • 503Video generation is not configured, or the daily usage count could not be read.

get/api/design/video/content

Stream a completed video

Streams the video of a completed job the caller submitted; a completed poll returns this address as result.video.url. Session-authenticated; read-only, so no same-origin check and no rate limit (a media element makes several requests per play). The platform reads the video from the provider server-side, so no provider address or credential reaches the caller. Authorized exactly as the poll is: a job the caller did not submit, one older than the 24-hour ownership window, or one asked for under another provider is answered 404. A job that no poll has yet reported completed is answered 409. The body is streamed, not buffered, with the provider's video media type (parameters dropped); any other media type is refused 502. One byte range (bytes=first-last, bytes=first- or bytes=-suffix) is passed on to the provider, and any other Range value is ignored, so the whole video is sent. The answer is 206 only when the provider itself answers the range; a provider that does not support ranges sends the whole video (200), and seeking is then limited to what the browser has already loaded. Job ownership is not shared across server instances, as for the poll.

Authentication: Session cookie required (same-origin enforced for state-changing requests).

Parameters

NameInTypeRequiredDescription
jobIdquerystringRequiredThe jobId from the submit response.
providerquerystringOptionalOptional: the provider from the submit response. Omitted, the job's own provider is used. A provider that is not registered is answered 400; one other than the job's own provider is answered 404.
RangeheaderstringOptionalOptional: one byte range. Several ranges, another unit or a reversed range are ignored.

Responses

  • 200The whole video.
  • 206The requested byte range, as the provider answered it.
  • 400jobId is missing, empty or longer than 256 characters, or provider is not a registered provider.
  • 401No valid session.
  • 404No job with this id was submitted by the caller (or its ownership record expired), or provider is not the provider the job was submitted to. One body for all three, so a job's existence is never confirmed.
  • 409The job exists but no poll has reported it completed yet.
  • 416The provider cannot satisfy the requested range.
  • 500Unexpected failure loading the video (reference returned).
  • 502The provider failed, did not answer in time, redirected, answered something other than a video media type, or sent malformed range or encoding headers. A fixed message is returned; the provider's own text is not.
  • 503Video generation is not configured.

Billing

Payment-provider webhooks. Server-to-server and signature-authenticated; not callable by customers.

post/api/billing/webhook

Stripe webhook receiver (not callable by customers)

Server-to-server endpoint called by Stripe; not callable by customers and not part of the customer API. There is no session and no same-origin check: the Stripe-Signature header (HMAC-SHA256 over the raw body with the platform webhook secret, 300-second replay window) is the authentication, and it is checked before the body is parsed or logged. DORMANT: a verified event is logged (metadata only) and acknowledged; no plan or entitlement change is made. Every event type is acknowledged the same way - there is no unknown-event refusal.

Authentication: Stripe-Signature header (server-to-server webhook; not callable by customers).

Parameters

NameInTypeRequiredDescription
Stripe-SignatureheaderstringRequiredStripe's t=<timestamp>,v1=<signature> header. Missing or invalid: 400.

Request body (application/json)

FieldTypeRequiredDescription
idstringRequired
objectstringRequired
typestringRequiredAny Stripe event type; none is refused.
creatednumberRequired
livemodebooleanRequired
api_versionstring | nullOptional
dataobjectRequired

Responses

  • 200Verified and acknowledged (any event type).
  • 400The body could not be read or is not valid UTF-8; the signature is missing, invalid or outside the replay window (signature verification failed); or a verified body is not a well-formed Stripe event (malformed event payload, reference returned).
  • 413The body (by its declared Content-Length, or by the bytes actually read) exceeds 1 MiB.
  • 503Webhook verification is not configured on the server (reference returned).