Errors
Every refusal is JSON with an error field, and refusals arrive in a fixed order: the gateway first, then your account standing, then the request itself, then the work. Two rules hold everywhere:
- A refusal before generation never costs credits. Only a captured success bills you; failed generation is released in full.
404never confirms existence. A resource that is not yours answers exactly like one that does not exist.
Gateway refusals
These can happen on any endpoint, before anything else is considered.
| Status | Body | When it happens | What to do |
|---|---|---|---|
404 | {"error": "Not found"} on every path | The TrueTone API is disabled on the server itself. No request shape causes this, and you should never see it in normal service | Contact support; nothing on your side causes or fixes it |
401 | {"error": "Invalid API key"} | Missing or malformed Authorization header, unknown key, revoked key, expired key, or a key with no recognized Capability Groups. All deliberately identical | Check the header shape, then mint or rotate the key in the dashboard |
403 | {"error": "This API key does not grant the <name> capability", "requiredCapability": "<id>"} | The key was not issued the endpoint’s Capability Group | Mint a key that carries the named group |
403 | {"error": "Your TrueTone account role no longer includes the <name> capability", "requiredCapability": "<id>"} | The key carries a role-sensitive group but your live account role no longer includes it | Re-minting will not help; your role has to change |
429 | {"error": "Rate limit exceeded", "message": "...", "retryAfter": <seconds>} plus X-RateLimit-* and Retry-After headers | Too many requests in this key’s window for this endpoint | Wait the full Retry-After, then back off. Costs nothing |
Account and budget refusals
The key is valid; the account or the budget said no.
| Status | Body | When it happens | What to do |
|---|---|---|---|
403 | {"error": "API key has no organization binding"} | The key’s organization link is gone, for example after an organization was deleted | Mint a fresh key from an account with an organization |
402 usually; 401, 403, or 500 by policy | {"error": "...", "code": "<denial code>", "message": "...", "redirectTo": "...", "requiresSetup": <bool>, "subscriptionStatus": "..."} | The account’s subscription standing refuses paid work, for example subscription_inactive. There is no read carve-out; a canceled account reads nothing here | Branch on code; resolve billing in the dashboard |
403 | {"error": "Forbidden", "code": "entitlement_scope_mismatch", "message": "..."} | The key’s recorded organization no longer matches the owner’s live organization | Mint a fresh key under the current organization |
429 | {"error": "Monthly spending cap reached", "currentSpend": <n>, "spendingCap": <n>, "billingPeriod": "YYYY-MM"} | The organization’s monthly cap is exhausted. Checked before reservation; nothing held | Raise the cap or wait for the period to roll |
402 | {"error": "Monthly credit allowance reached", "code": "allocation_exceeded", "actionKey": "...", "requiredCredits": <n>, "allowanceRemaining": <n>} | Your per-member allowance is spent while the organization still has credits | Ask your org admin for a larger allocation |
402 | {"error": "Insufficient AI credits", "code": "insufficient_credits", "actionKey": "...", "requiredCredits": <n>, "availableCredits": <n>} | The organization is out of credits | Top up or wait for the monthly reset |
Request refusals
The request itself was defective. All of these are refused before anything runs or reserves.
| Status | Body | When it happens | What to do |
|---|---|---|---|
400 | {"error": "Invalid <request> ...", "issues": {"<field>": ["<message>"]}} | A field failed validation, or the body was not JSON | Fix the named fields; issues is keyed per field |
400 | {"error": "invalid_request", "message": "..."} | A scheduling request missing or misusing a required field; the message states exactly which | Follow the message |
400 | {"error": "unsupported_creation_type", "requested": "...", "supported": [...]} | Asking the generation endpoint for a type it does not make, such as an audiogram | Use the endpoint the response points you toward |
400 | {"error": "conflicting_parameters", "message": "..."} | Asking a social post to both generate an image and use one from your library | Pass one image instruction, not both |
400 | {"error": "<refusal reason>", "message": "..."} | An image URL the asset door refuses: url_not_https, url_resolves_privately, url_unreachable, redirect_not_https, too_many_redirects, not_an_image, image_too_large | The reason is stable; the message says how to fix it |
400 | {"error": "template_too_complex"} | A transform whose protected regions exceed what the rewriter will safely handle | Split the document or lock fewer regions |
Resource refusals
| Status | Body | When it happens | What to do |
|---|---|---|---|
404 | {"error": "not_found"} | The id does not exist, is malformed, or belongs to someone else. Indistinguishable on purpose | Check the id against your own listings |
State and outcome refusals
The request was well-formed; the work could not be done, or could not be done safely.
| Status | Body | When it happens | What to do |
|---|---|---|---|
409 | {"error": "voice_not_configured", "message": "..."} | Audio was requested but the account has no cloned voice yet | Record a voice sample in the dashboard first |
409 | {"error": "not_schedulable", "message": "..."} | The creation cannot go on the calendar as it stands: too soon, missing a title or caption, video not recorded, blog not published | The message names the missing precondition |
422 | {"error": "locked_region_not_found", "unmatched": [...]} | A locked region you named does not occur in source_text | Send regions exactly as they appear in the text |
422 | {"error": "unbalanced_conditional"} | Template conditional syntax could not be read safely | Balance the conditional markers before sending |
422 | {"error": "unclosed_lock_sentinel"} | A lock marker was opened and never closed | Close every lock marker |
422 | {"error": "preservation_failed", "missing": [...], "duplicated": [...], "unknownPlaceholders": [...]} | The rewrite damaged a protected span. Your credit is released | Retry; consider fewer or larger locked regions |
422 | {"error": "placement_failed", "contained": [...], "reordered": [...]} | Every protected span survived but one landed somewhere unsafe or out of order. Your credit is released | Retry |
502 | {"error": "generation_failed"} | The model call failed or returned nothing. Your reservation is released; never billed | Retry with backoff |
502 | {"error": "distribution_failed", "message": "...", "retryable": <bool>} | The social publishing provider refused or could not be reached | If retryable is true, retry later; if false, the message names what to fix, usually a connection in the dashboard |
500 | {"error": "Internal Server Error"} | Something unexpected on our side. Details are logged server-side, never leaked | Retry with backoff; report it if it persists |
One success can still carry a failure: scheduling a post returns 200 even when the publishing provider rejects it, with the stored post carrying status: "failed" and an errorMessage. Always read post.status after scheduling. The walkthrough covers this in Seed to scheduled post.