> For the complete documentation index, see [llms.txt](https://docs.auray.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.auray.ai/api-reference/errors.md).

# Errors

Every refusal has the same shape, and the `docs` field points at this page:

```json
{
  "error": "insufficient_credits",
  "message": "Not enough credits for this request.",
  "required": 29,
  "docs": "…/errors#insufficient_credits",
  "request_id": "54b4754a-3e17-4629-adb1-7d3f067e97d3"
}
```

Match on `error`, never on `message` — the sentence is written for a person and may be reworded. `request_id` is on every response, success or not, and in the `X-Request-Id` header; quote it if you ask us about one.

This page is generated from the routes themselves, so a code that exists here is a code the API can actually return, and one it can return cannot be missing from here.

## Before your request is looked at

These are decided from the key and the headers alone, before any body is parsed and before anything is charged.

### api\_disabled

**503**

The public API is switched off on this deployment. It is answered before your key is even parsed, so a valid key and an invalid one look the same here.

**What to do.** Nothing on your side. The surface is not open yet; there is no key that gets past this.

### browser\_origin\_refused

**403**

Your request carried an `Origin` header. This API is not callable from a browser — no `Access-Control-*` header is ever sent, so a browser could not use the response anyway, and a key in front-end code is a key that has been published.

**What to do.** Call from your server. Some HTTP clients set `Origin` by default even outside a browser; if you are getting this from a backend, remove that header.

### insufficient\_scope

**403** · carries `required`, `scopes`

The key is valid but was not minted with the scope this route needs. `required` is what was wanted; `scopes` is what the key has.

**What to do.** Scopes cannot be added to an existing key. Mint a new one with the scope you need — and note that listing webhooks needs `jobs:read`, not `webhooks:write`.

### invalid\_api\_key

**401**

The key is not well formed, or no live key matches it. Length, prefix, alphabet and a checksum are all checked before any database read, so a typo is usually caught here.

**What to do.** Check the key was copied whole — it is 71 characters. If it looks right, it may have been deleted; mint a new one in Settings.

### key\_expired

**401**

This key is past its expiry. Every key has one — 30, 90, 180 or 365 days — and it is not optional.

**What to do.** Mint a new one, and set a calendar reminder before its expiry rather than after.

### key\_revoked

**401**

This key was revoked.

**What to do.** Mint a new one. A revoked key never comes back.

### keys\_not\_configured

**503**

This deployment has no key pepper configured, so no key can be verified.

**What to do.** Nothing on your side. Tell us, with the `request_id`.

### missing\_authorization

**401**

No `Authorization` header.

**What to do.** Send `Authorization: Bearer auray_sk_…`. A bare key without `Bearer` is also accepted.

## Rate limits and budgets

Four token buckets — `read`, `write`, `generate` and `egress` — refill continuously and are reported on every response in both `RateLimit-*` and `X-RateLimit-*` headers. A refusal here is about pace, except where it is about a plan, and the two look different on purpose.

### body\_too\_large

**413** · carries `limit_bytes`

The request body is over 1 MiB. It is checked against `content-length` first and then while streaming, so an unset length does not get past it.

**What to do.** Send less. Images belong behind `POST /v1/uploads`, which returns a signed URL you PUT the bytes to directly, precisely so they never travel through a JSON body.

### key\_credit\_ceiling

**403** · carries `ceiling`, `spent`, `required`

This key has its own monthly credit budget and this request would exceed it. The account may still have plenty; the key does not.

**What to do.** Use a different key, or wait for the period to roll over. To lift the cap, mint a replacement key on the current plan and revoke this one. A ceiling of zero means no cap and never refuses.

### limiter\_unavailable

**503**

The rate limiter itself could not be reached, and this API refuses rather than waving traffic through unmetered.

**What to do.** Retry after 5 seconds, as the `Retry-After` header says.

### plan\_has\_no\_api\_generate

**403** · carries `plan`, `bucket`

Your plan's `generate` bucket has a capacity of zero, which is not the same as being out of tokens. Free accounts cannot start a render from the API at all.

**What to do.** Upgrade. Waiting does not help — a bucket of zero never refills into one.

### rate\_limited

**429** · carries `bucket`, `retry_after`

You spent the bucket named in `bucket` faster than it refills. Buckets are per key and refill continuously rather than resetting on a schedule.

**What to do.** Wait `retry_after` seconds — it is also in the `Retry-After` header — and pace future requests against the `RateLimit-Remaining` header rather than retrying blind.

### request\_too\_large

**413** · carries `plan`, `bucket`, `limit`

The request costs more of this bucket than the bucket can ever hold.

**What to do.** Ask for less in one call — fewer images, a shorter clip — or upgrade to a plan with a larger bucket.

## The request itself

The envelope was wrong: the body, a query parameter, or a value we could not read.

### conversation\_required

**400**

A chat turn needs a conversation to belong to.

**What to do.** Create one with `POST /v1/chat/conversations` and pass its id, or omit the id and let the turn open one.

### invalid\_before

**400**

`before` is not an ISO 8601 timestamp.

**What to do.** Pass the `next_before` value from the previous page back unchanged.

### invalid\_body

**400**

The body parsed as JSON but was not an object — an array, a string or `null`.

**What to do.** Send a JSON object.

### invalid\_idempotency\_key

**400**

`idempotency_key` must match `[A-Za-z0-9._-]{1,64}`. Colons in particular are refused, because credit keys are built by joining fields with them and a colon here could shape another account's key.

**What to do.** Use a UUID, or any opaque string from that alphabet.

### invalid\_json

**400**

The body is not JSON.

**What to do.** Check the serialiser, and that `Content-Type: application/json` is set.

### invalid\_limit

**400** · carries `max`

`limit` must be between 1 and 100.

**What to do.** Ask for at most 100 and follow `next_before` for the rest.

### invalid\_products

**400** · carries `allowed`

A webhook's `products` array names something that is not a product.

**What to do.** Use values from `allowed`, or send an empty array to receive every product.

### invalid\_title

**400**

A conversation title is over 200 characters.

**What to do.** Shorten it, or send an empty title and let the first message name the conversation.

### invalid\_url

**409** · carries `reason`

A webhook URL was refused. `reason` distinguishes them: not a URL, not https, a private or local host, or credentials embedded in it. As a 409 rather than a 400 it means the URL was fine when you registered it and is not any more.

**What to do.** Use a public https URL with no user info. Private addresses are refused in every form we can recognise, including IPv4-mapped IPv6.

### invalid\_wait

**400** · carries `max`

`?wait=` must be a whole number of seconds from 1 to 300.

**What to do.** Send an integer in range, or omit it and poll.

### unknown\_content\_type

**400** · carries `accepted`

Uploads accept `image/png`, `image/jpeg` and `image/webp` only.

**What to do.** Convert first, and declare the same type on the PUT that follows.

### unknown\_effort

**400**

The chat effort named does not exist.

**What to do.** Use `light`, `balanced` or `deep`.

### unknown\_model

**400** · carries `allowed`

The chat model named does not exist.

**What to do.** Use `qwen` or `kimi`.

### unknown\_product

**400** · carries `allowed`, `products`

The product named is not one this route serves.

**What to do.** Use one of the values in `allowed`.

### wait\_unsupported

**400** · carries `product`

Video does not accept `?wait=`. A render takes minutes, and holding a connection open for one would time out somewhere between us and you rather than at a moment either of us chose.

**What to do.** Poll `GET /v1/jobs/{id}`, or register a webhook and be told.

## What each product accepts

Every one of these is a 400 from the product's own parser, decided before capacity is checked and long before anything is charged.

### control\_data\_invalid

**400**

`control_data` could not be read as the control type you named.

**What to do.** Check the shape against the tier's documentation.

### control\_not\_supported\_by\_tier

**400**

That control does not apply to that tier.

**What to do.** Use a tier that accepts it, or drop the control.

### control\_type\_required

**400**

A controlled 3D tier needs `control_type`.

**What to do.** Name the control you are supplying.

### duration\_out\_of\_range

**400** · carries `min`, `max`

The duration is outside what the product renders — video 4 to 15 seconds, music 10 to 300.

**What to do.** Send a value between `min` and `max`.

### duration\_required

**400**

Music needs `duration_seconds`.

**What to do.** Send a whole number of seconds.

### fbx\_needs\_a\_rigged\_tier

**400**

`out_format: "fbx"` only means something for a tier that produces a skeleton.

**What to do.** Ask for a rigged tier, or take `glb`.

### image\_required

**400**

3D starts from an image, and none was given.

**What to do.** Upload one with `POST /v1/uploads` and pass the `path` it returns — not a URL of your own.

### image\_unreadable

**400**

The uploaded image could not be decoded.

**What to do.** Re-upload as PNG, JPEG or WebP, and check the PUT actually returned 200.

### lyrics\_too\_long

**400**

The lyrics are over the limit.

**What to do.** Shorten them, or send none for an instrumental.

### num\_images\_invalid

**400**

`num_images` is outside 1 to 4.

**What to do.** Ask for at most four. Each is priced separately.

### prompt\_required

**400**

The prompt is missing or empty after trimming.

**What to do.** Send one.

### prompt\_too\_long

**400**

The prompt is over the product's limit — 7,000 characters for video, 4,000 for photo.

**What to do.** Shorten it. Length past the limit does not improve the result anyway.

### text\_required

**400**

A chat turn with no text.

**What to do.** Send some.

### text\_too\_long

**400**

A chat message is over 24,000 characters.

**What to do.** Split it, or summarise what came before.

### unknown\_aspect

**400** · carries `aspect_ratios`

The aspect ratio is not one this product renders.

**What to do.** Use a value from `aspect_ratios`. For video, only `16:9`, `9:16` and `1:1` have warm containers; the rest pay a cold start.

### unknown\_resolution

**400**

Photo renders at 1024 or 2048 only.

**What to do.** Pick one of the two.

### unknown\_style

**400**

The style named is not one we carry.

**What to do.** Omit it, or use one from `GET /v1/me`.

### unknown\_tier

**400** · carries `tiers`

The tier named does not exist. Video has two — `fast` and `pro`; a third called `standard` was retired because it rendered identically to `pro` on slower hardware and cost more.

**What to do.** Use a value from `tiers`. Omitting it is fine and gives you the default.

## What your plan allows

A 403 rather than a 402: the account is in good standing, it simply does not include this. Credits will not help; a different plan will.

### duration\_not\_in\_plan

**403** · carries `plan`, `max`

The duration is within what the product can render but beyond what your plan buys.

**What to do.** Ask for at most `max` seconds, or upgrade.

### images\_not\_in\_plan

**403** · carries `plan`, `max`

More images per request than your plan allows.

**What to do.** Ask for at most `max`, or upgrade.

### lyrics\_not\_in\_plan

**403** · carries `plan`

Vocals are not on your plan.

**What to do.** Send no lyrics for an instrumental, or upgrade.

### plan\_has\_no\_chat

**403** · carries `plan`

Your plan does not include chat.

**What to do.** Upgrade to Pro or above.

### plan\_has\_no\_music

**403** · carries `plan`

Your plan does not include music.

**What to do.** Upgrade to Pro or above.

### plan\_has\_no\_photo

**403** · carries `plan`

Your plan does not include image generation.

**What to do.** Upgrade to Pro or above.

### plan\_has\_no\_threed

**403** · carries `plan`

Your plan does not include 3D.

**What to do.** Upgrade to Pro or above.

### plan\_has\_no\_video

**403** · carries `plan`, `upgradeTo`

Your plan does not include video.

**What to do.** Upgrade to the plan named in `upgradeTo`.

### resolution\_not\_in\_plan

**403** · carries `plan`, `max`

2048 is not on your plan.

**What to do.** Render at 1024, or upgrade.

### tier\_not\_in\_plan

**403** · carries `plan`, `tier`

That tier is not on your plan.

**What to do.** Use a tier your plan includes, or upgrade.

### upscale\_not\_in\_plan

**403** · carries `plan`

`upscale_2k` is refused on every plan today. The 2K pass is a call out to a hosted service this deployment has no key for, so it is switched off rather than half-working.

**What to do.** Drop the flag. There is no plan that currently accepts it — this is not an upgrade prompt.

## Where the model licences reach

3D runs on Tencent Hunyuan3D, whose licence does not cover every country. That is a licence boundary rather than a policy of ours, which is why it is neither a 403 nor a 404.

### region\_blocked

**451** · carries `region`

The 3D model's licence does not reach the country your account declares.

**What to do.** Nothing on your side, and this is not a plan problem — no upgrade changes it. The other four products are unaffected.

### region\_invalid

**428** · carries `region`

The declared country is not one we recognise.

**What to do.** Set a valid ISO country code in Settings.

### region\_required

**428**

3D needs to know which country your account operates from, and none has been declared.

**What to do.** Set it in Settings. It is one field, and it is asked once.

## When there is no room

Every product has a fixed number of GPU containers and a reserved share for API traffic, so that a script cannot take the browser's seats. None of these charge anything: admission runs before the money.

### service\_busy

**429 or 503**

As a 429, every container this product has is busy, or the share reserved for API traffic is full. As a 503, our admission check itself could not run and we refused rather than guessing. 3D is the tightest by far: it runs one API job at a time worldwide.

**What to do.** Retry with backoff. Nothing was charged either way.

### tier\_busy

**429** · carries `tier`, `limit`

Every container of this video tier is rendering. The tiers scale independently, so this says nothing about the others.

**What to do.** Retry shortly, or submit to the other tier — `fast` is roughly a quarter of the price and usually has room when `pro` does not.

### too\_many\_jobs

**429** · carries `limit`, `plan`, `tier`

You already have `limit` jobs of this kind in flight. The cap is per account, and for video it is per tier.

**What to do.** Wait for one to finish. Webhooks are cheaper than polling for knowing when.

## Credits

### insufficient\_credits

**402** · carries `required`, `balance`

The account does not hold `required` credits.

**What to do.** Top up, or ask for something cheaper — a shorter clip, fewer images, the `fast` tier.

### refunded

**402**

The job's credits were given back, so its output is no longer yours to fetch. This is what a failed or cancelled render looks like from the assets route.

**What to do.** Nothing to do. Read the job itself for why it failed.

## Reading, cancelling and fetching a job

### cancel\_failed

**502**

The rendering service did not accept the cancel.

**What to do.** Retry after 5 seconds. If it keeps failing the job will settle on its own.

### cancel\_raced

**409**

The job settled while the cancel was in flight.

**What to do.** Read the job — it has a terminal status now.

### cancel\_refused

**409** · carries `status`

The job is past the point where cancelling would save anything.

**What to do.** Let it finish; you will be charged for what was produced.

### cancel\_unsupported

**400** · carries `product`

This product cannot be cancelled once started.

**What to do.** Let it finish. Chat turns in particular settle in seconds.

### conversation\_not\_found

**404**

No such conversation, or not yours.

**What to do.** Check the id, or create a new conversation.

### expired

**410** · carries `reason`

The stored object is gone. Output is not kept forever.

**What to do.** Re-render. Fetch and store what you need when a job finishes rather than later.

### in\_flight

**409** · carries `message_id`

This idempotency key is already running. Reusing it is how a retry finds the original rather than starting a second one.

**What to do.** Poll the job it names. Use a fresh key only when you actually want a second render.

### key\_settled

**409** · carries `message_id`

This idempotency key already finished. Keys are not reusable once settled, which is what stops a replay charging twice.

**What to do.** Read the finished job, or send a new key.

### not\_found

**404**

No such job, or not yours. The two are deliberately the same answer — telling them apart would let anyone map which ids exist.

**What to do.** Check the id. Job ids are `<product>_<idempotency_key>` and are known before dispatch.

### not\_ready

**409** · carries `status`

The job has not finished, so it has no assets yet.

**What to do.** Wait for a terminal status. `poll_after_seconds` on the job read says how long is worth waiting.

### product\_not\_available

**404** · carries `product`

That product is not configured on this deployment.

**What to do.** Nothing on your side.

### settle\_failed

**500**

The cancel reached the service but we could not record the outcome.

**What to do.** Read the job again in a moment. Tell us if it stays open.

### upstream\_lost

**410**

The rendering service no longer has any record of this job. It keeps them for a few days only, so an old id reads the same as one that never existed.

**What to do.** Re-submit. If the job was recent, tell us with the `request_id`.

## Webhook endpoints

### too\_many\_webhooks

**409** · carries `limit`

An account may hold five endpoints that are not dead.

**What to do.** Delete one you no longer use. Re-registering the same URL resets it rather than adding another.

### webhook\_delete\_failed

**500**

The delete did not go through. It is reported rather than swallowed, because an endpoint that is still there goes on receiving callbacks.

**What to do.** Retry. Read `GET /v1/webhooks` to see whether it survived.

### webhook\_not\_created

**500**

The endpoint could not be stored.

**What to do.** Retry. Tell us with the `request_id` if it persists.

### webhook\_read\_failed

**500**

Your endpoint could not be read. This is a failed lookup rather than a missing endpoint — the two used to share a `not_found`.

**What to do.** Retry. Nothing about the endpoint changed.

## When it is our fault

Nothing here is caused by your request. Where a charge had already landed it has been reversed, and the entry says so.

### chat\_not\_configured

**503**

Chat is not configured here.

**What to do.** Nothing on your side.

### chat\_start\_failed

**500**

The turn could not be recorded.

**What to do.** The charge was reversed. Retry.

### conversation\_not\_created

**500**

The conversation could not be stored.

**What to do.** Retry.

### credit\_reserve\_failed

**500**

Credits could not be reserved.

**What to do.** Retry. Nothing was charged.

### job\_read\_failed

**500**

Your job could not be read. It is not a statement that the job is missing — a failed lookup used to answer `404 not_found`, which said the id was wrong or not yours when neither had been established.

**What to do.** Retry. The job is untouched, and nothing was charged or refunded by the attempt.

### job\_record\_failed

**500**

The job was dispatched but we could not record it.

**What to do.** The charge was reversed. Retry with the same idempotency key.

### music\_not\_configured

**503**

Music is not configured here.

**What to do.** Nothing on your side.

### no\_signing\_credential

**503**

We could not sign a download URL.

**What to do.** Retry. Tell us with the `request_id` if it persists.

### not\_configured

**503**

This deployment is missing something it needs to answer.

**What to do.** Nothing on your side.

### photo\_not\_configured

**503**

Image generation is not configured here.

**What to do.** Nothing on your side.

### profile\_read\_failed

**500**

Your account could not be read.

**What to do.** Retry. Nothing was charged.

### service\_unavailable

**502**

The rendering service did not answer.

**What to do.** Retry with backoff. Nothing was charged.

### threed\_not\_configured

**503**

3D is not configured here.

**What to do.** Nothing on your side.

### upload\_url\_failed

**503**

A signed upload URL could not be created.

**What to do.** Retry.

### video\_not\_configured

**503**

Video is not configured here.

**What to do.** Nothing on your side.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.auray.ai/api-reference/errors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
