AI Content Drop API Error Codes
Errors are always JSON, on every surface and every status code. An HTML error page from this API would be a bug.
{ "error": { "code": "insufficient_credits", "message": "This generation needs 22 credits; the account holds 4." } }
The codes
Codes are lower snake_case. Branch on error.code, never on the message text — the message is written for a human reading a log and may be reworded.
| Code | HTTP | Cause | What to do |
|---|---|---|---|
invalid_request |
400 | Missing or malformed field | Fix the body; the message names the field |
invalid_json |
400 | Body is not parseable JSON | Send a complete object with Content-Type: application/json |
missing_query |
400 | /ask called with no question |
Supply q (GET) or query (POST) |
unauthorized |
401 | No key, or a revoked or malformed one | Read WWW-Authenticate; see authentication |
unauthenticated |
401 | Session surface reached without a session | Use /v1; most of it needs no credential |
invalid_client |
401 | Agent client id or secret does not match | Re-register at /agent/auth/register |
forbidden |
403 | Authenticated, but not your object | Do not retry |
not_found |
404 | No such job or document | Re-read the catalogue |
unknown_endpoint |
404 | No API endpoint at that path | GET /v1 lists the surface |
unknown_model |
404 | No such model id | Re-read /v1/models; check id normalisation |
method_not_allowed |
405 | Right path, wrong verb | The message names the verbs that work |
insufficient_credits |
402 | Balance below the model cost | Top up, or pick a cheaper model |
idempotency_conflict |
409 | Same Idempotency-Key, different body |
Use a new key |
payload_too_large |
413 | Body over the endpoint's limit | Split the work; see POST /v1/batch |
query_too_large |
413 | GraphQL query over 8,000 characters | Split the query |
rate_limited |
429 | Too many requests | Wait Retry-After seconds |
safety_rejected |
400 | Content-safety gate refused the prompt | Rewrite the prompt; nothing was charged |
unsupported_operation |
400 | Operation not available in a batch | Call the endpoint directly |
operation_failed |
400 | One batch item failed | Read that item's own error |
provider_error |
502 | Upstream model provider failed | Retry is reasonable; nothing was charged |
internal_error |
500 | Our bug | Retry once, then tell us |
The two envelopes
Agent-facing paths — /v1, /graphql, /ask, /agent/*, /mcp, /.well-known/* — always answer with { "error": { "code", "message" } }.
The two generation endpoints are path aliases onto the handlers the website itself calls, so a validation failure from those two still carries the website's older { "error": "message" } string shape. A client that reads error.code when error is an object and falls back to error when it is a string handles both. Everything else on /v1 is the structured envelope.
What a failure costs
Nothing. Billing is post-deduct: credits are charged when a generation succeeds. A safety refusal, a provider failure, and a job that times out all cost zero, which is why there is no refund endpoint in this API — there is nothing to refund.
A job that fails after it started shows status: "failed" with creditsUsed: 0 and an error_message you can read.
In GraphQL
Two channels, deliberately. Request-level failures use the standard top-level errors array with the same code in extensions.code. Failures you can act on come back as typed UserError values inside the payload, so a partial success is still a success. See the GraphQL docs.
In MCP
A tool that fails returns a normal result with isError: true and the reason as text, rather than a JSON-RPC error — the model needs to read the message to correct its own call. Protocol-level problems (unknown method, malformed message) use JSON-RPC error codes.