API reference
The Corte REST API is the platform without the UI: the same model catalog, Applications, Workflows, storage, and transcription that power the app, callable from a script, a backend, or an agent.
This section is the reference — every resource, parameter, and response shape. If you want the narrative version first, start with the API overview guide.
https://api.corte.so
Every path in this reference is relative to that base URL. /v1 in the path is
the API version; it is the only version, and existing fields are additive-only
within it.
Authentication
Send an API key as a bearer token on every request:
curl https://api.corte.so/v1/account \
-H "Authorization: Bearer corte_sk_…"
Corte stores only a SHA-256 hash of the key, so the secret is shown exactly once — at creation. A key carries the full authority of the account that made it: every endpoint in this reference accepts key auth, and any generation, LLM step, workflow run, or transcription it starts spends that account's credits.
Requests with no credentials, an unknown key, or a revoked key fail with
unauthenticated (401). Two endpoints in this reference are the exception and
need no key: GET /v1/health and
GET /v1/plans.
:::note Browser sessions and plugin tokens
The app itself authenticates with an eli_session httpOnly cookie, and
sandboxed plugins use per-install corte_pk_ tokens restricted to a handful of
read-only routes. Neither is part of the public API — build against
corte_sk_ keys.
:::
Requests
Send JSON bodies with Content-Type: application/json. Path parameters are
written :id throughout this reference. Query parameters are documented per
endpoint; unknown query parameters are ignored.
The request body limit is 32 MB. Media never travels through a JSON body —
it goes through Files and is referenced afterwards by
storageId.
Responses
Successful responses are 200 with a JSON object. Resources are nested under
a named key rather than returned bare, so responses stay extensible:
{ "job": { "id": "…", "status": "queued" }, "estimatedCredits": 24 }
Collections follow the same rule and use a plural key — { "models": [...] },
{ "workflows": [...] }, { "jobs": [...] }. Deletes and other side-effect-only
endpoints return { "ok": true }.
Timestamps are ISO 8601 UTC strings (2026-07-28T14:03:11.482Z). Ids are UUIDs
unless documented otherwise — model ids (kling-3) and Application ids
(flatlay-to-model) are stable slugs.
Signed media URLs expire
Any url or resultUrls field holding media is a presigned read URL with a
finite lifetime. Fetch a resource again to get freshly signed URLs; don't
persist them. The durable handle is storageId (or resultStorageIds on a
generation job) — those never expire, and every endpoint that takes media takes
a storageId.
Errors
Errors use conventional HTTP status codes and a single body shape:
{ "error": { "code": "insufficient_credits", "message": "…" } }
Branch on code, not on message — messages are human-facing and change. The
full list of codes is in Errors.
Asynchronous work
Generations, transcriptions, and workflow runs are asynchronous. The creating call returns immediately with a record in a non-terminal state; you poll until it settles. There are no webhooks.
| Resource | Create | Poll | Terminal states |
|---|---|---|---|
| Generation | POST /v1/generations | GET /v1/generations/:id | succeeded · failed · canceled |
| Transcription | POST /v1/transcriptions | GET /v1/transcriptions/:id | succeeded · failed |
| Workflow run | POST /v1/workflows/:id/execute | GET /v1/workflows/:id/runs/:runId | complete · error · stopped |
A 2–3 second poll interval is a good default. Image generations typically settle in seconds, video in one to several minutes, and workflow runs take as long as the slowest chain of nodes.
Workflow runs are the exception to poll-only: POST /v1/workflows/:id/execute
accepts wait: true to block until the run finishes. Practical for short
pipelines, risky for long ones.
A workflow run's nodeResults fills in incrementally while status is
running, so polling it streams per-node progress rather than flipping from
empty to complete.
Pagination and sorting
Every unbounded list follows one contract: it accepts limit and offset, and
returns a total that counts every matching row, not the page. Lists are newest
first; where an alternate order is useful, a sort parameter selects it.
What varies per endpoint is only the limit default, which follows how the
list is consumed:
-
Activity feeds default to a page — poll-and-page surfaces.
Endpoint Default Max GET /v1/generations50 200 GET /v1/workflows/:id/runs20 100 GET /v1/usage100 500 GET /v1/projects/:id/usage50 200 -
Catalog/gallery lists return the full list when
limitis omitted — their primary consumers (media pickers, the workflow gallery, the Studio catalog) render everything. Passlimit(max 500) to page.Endpoint Sort options GET /v1/modelscatalog order GET /v1/applicationscatalog order GET /v1/workflowsupdated(default) ·created·nameGET /v1/storage/usagenewest (default) · sizeGET /v1/projects/:id/storagenewest (default) · size
The one exception is GET /v1/api-keys, served whole — accounts are
hard-capped at 20 keys.
Credits and billing
Work that costs money is priced in credits — 100 credits = $1. Endpoints
that spend credits check the balance before doing the work and return
insufficient_credits (402) if the estimate doesn't fit, so a 402 means nothing
was charged.
Creation responses for generations include estimatedCredits, which is the
amount reserved against the balance. The actual charge lands on the job's
costCredits when it settles.
Who pays. By default, the account that owns the key. When a request carries
a projectId for a shared project, the
project owner pays and the caller is recorded as the actor. If the owner's
balance is short, the error is owner_insufficient_credits (402) rather than
insufficient_credits.
Storage is a hard quota with no overage billing: at the cap, uploads and imports
fail with storage_limit (402) until files are deleted or the plan is upgraded.
Generation results always save.
Every charge appears in the ledger at
GET /v1/usage.
Rate limits
There are no per-key request limits today. Credits and the storage quota are the
real limits. Two 429s can still reach you, both upstream in origin:
rate_limited from a provider under load, and provider_unavailable (503) when
one is down — retry both with backoff.
Poll considerately. Nothing throttles a tight polling loop, but nothing rewards it either.
Resources
| Resource | What it covers |
|---|---|
| Models | The generation catalog: capabilities and credit pricing |
| Generations | Submit, poll, list, and cancel generation jobs |
| Applications | Run the 43 curated one-shot experiences |
| Workflows | Create, read, update, and delete node pipelines |
| Workflow runs | Execute pipelines with custom inputs and read per-node results |
| Files | Upload, import, list, and delete media; storage quota |
| Transcriptions | Word- and segment-level transcripts with speakers |
| Projects | Shared workspaces, members, pooled billing |
| Account & keys | Balance, plans, usage ledger, API key management |
| MCP tools | The same primitives as agent tools over Streamable HTTP |
| Errors | Every error code, cause, and how to handle it |