Errors
Every error — from any endpoint, and from every MCP tool — has the same body:
{
"error": {
"code": "insufficient_credits",
"message": "You need 240 more credits to run this."
}
}
code is a stable machine-readable string. message is written for humans and
will change; never parse it or branch on it.
Codes
| Code | Status | Cause | What to do |
|---|---|---|---|
invalid_params | 400 | The body or query doesn't match the endpoint contract — a missing field, an unknown storageId, a duration the model doesn't support. | Read message; it names the offending field and, for capability mismatches, lists the valid values. Fix and retry. |
unauthenticated | 401 | Missing, malformed, unknown, or revoked API key. | Check the Authorization: Bearer corte_sk_… header. If the key was deleted, issue a new one. Not retryable. |
insufficient_credits | 402 | The caller's balance can't cover the estimate. | Top up or upgrade. Nothing was charged. |
owner_insufficient_credits | 402 | Same, but for project-billed work — the project owner's balance is short, not the caller's. | The caller can't fix this; the owner must top up. |
plan_required | 402 | The model is paidOnly and the paying account has never bought anything. | Pick a model with paidOnly: false, or make a top-up or subscribe — either unlocks the full catalog. |
subscription_required | 402 | The action needs an active subscription. | Subscribe, or use a free-tier path. |
storage_limit | 402 | The account is at its plan's storage quota. Blocks new uploads and imports only. | Delete objects with DELETE /v1/storage/:storageId or upgrade. Generation results still save. |
forbidden | 403 | Authenticated, but not allowed: a contributor doing an owner-only action, a workspace with the feature disabled, deleting a default project. | Not retryable as-is. Check the caller's project role. |
not_found | 404 | Unknown id — or an id owned by someone else. Ownership failures are deliberately indistinguishable from missing rows. | Verify the id and that the key's account can reach it. |
conflict | 409 | A client-supplied id already exists (project creation), or a versioned write is stale. | Re-read current state and retry with fresh values. |
payload_too_large | 413 | Request body over 32 MB, or an upload over the 1 GB per-file limit. | Send media through Files rather than in a JSON body. |
rate_limited | 429 | An upstream provider rate-limited the request. Corte does not impose per-key limits. | Retry with exponential backoff. |
provider_unavailable | 503 | An upstream model or LLM provider is down or returned something unusable. | Retry with backoff; try another model if it persists. |
internal | 500 | An unhandled server error. | Retry once; if it repeats, contact support with the timestamp. |
Errors that cost nothing
Credit checks run before any work is submitted to a provider. A 402 of any
kind therefore means no credits were spent — the estimate simply didn't fit.
The same is true of invalid_params from capability validation: parameters are
checked against the model's declared capabilities before the job is created.
Failures that arrive later
A request can succeed and the work still fail, because generation is
asynchronous. A generation that reaches status: "failed" carries the reason in
errorMessage; a workflow run that reaches status: "error" carries per-node
detail in nodeResults[nodeId].detail. Neither is an HTTP error — the poll
returns 200 with a failed record.
Handling pattern
import time, requests
BASE = "https://api.corte.so"
HEAD = {"Authorization": f"Bearer {KEY}"}
def submit(body):
r = requests.post(f"{BASE}/v1/generations", json=body, headers=HEAD)
if r.status_code == 200:
return r.json()["job"]
code = r.json().get("error", {}).get("code")
if code in ("insufficient_credits", "owner_insufficient_credits", "plan_required"):
raise OutOfBudget(code) # nothing was charged; don't retry blindly
if code in ("rate_limited", "provider_unavailable", "internal"):
time.sleep(5) # transient upstream — back off and retry
return submit(body)
raise ValueError(r.json()["error"]["message"]) # your bug: fix the request
Three buckets cover it: fix the request (400/401/403/404/409/413), fix the budget (402), wait and retry (429/500/503).