Docs

Working with the API

Jobs

Two hosts, one job. Submit and follow it on the queue; read, cancel and download it on the platform.

A generation is a job. You submit it, it runs without you, and you collect it later.

#Two hosts, one job

The queue host follows the common IN_QUEUE / IN_PROGRESS / COMPLETED queue protocol, so a client written for it works unchanged (Compatibility). The platform host holds the account's view of the same job. They share every id.

HostDoes
POST /<address>queue.auray.runSubmits. Answers the queue envelope below.
GET /<address>/requests/{id}/statusqueue.auray.runWhere it is: IN_QUEUE, IN_PROGRESS, COMPLETED. Add ?logs=1 for progress lines.
GET /<address>/requests/{id}/status/streamqueue.auray.runThe same, as server-sent events until it completes. Each stream closes after a few minutes with no final frame; reconnect and keep reading, as the official clients do.
GET /<address>/requests/{id}queue.auray.runThe result once COMPLETED. Before then it answers 400 request_in_progress, as that protocol does.
PUT /<address>/requests/{id}/cancelqueue.auray.runAsks it to stop: 202 requested, 400 already completed, 404 unknown.
POST /<address>auray.runThe same submit, held open until the job settles or 300 seconds pass.
GET /v1/jobs/{id}api.auray.aiThe job as the account sees it: five statuses, settled, the charge.
GET /v1/jobs/{id}/assetsapi.auray.aiSigned download URLs. Metered by the megabyte.
DELETE /v1/jobs/{id}api.auray.aiCancel, and answer what the job became.
GET /v1/jobsapi.auray.aiThe account's jobs, newest first.

{id} is the request_id the submit answered: <product>_<your idempotency key>.

#Five statuses, three states

queued ──▶ running ──▶ succeeded
                   ├─▶ failed
                   └─▶ cancelled
StatusQueue stateMeans
queuedIN_QUEUEAccepted and waiting. Nothing has started.
runningIN_PROGRESSRunning now.
succeededCOMPLETEDFinished. assets holds the output.
failedCOMPLETEDStopped. error_code says why, and the charge is reversed.
cancelledCOMPLETEDYou stopped it. What hadn't been made yet is refunded.

Only queued and running are open. Once a job reaches any of the other three it never changes again, and settled: true on the platform read says the charge is final too.

A failed job is COMPLETED, which reads wrong and is right: it has finished queueing and finished running, and the failure travels in error rather than in the state. A client that loops until COMPLETED terminates on every outcome. A fourth FAILED state is exactly the change that would make such a client loop for ever.

#Submitting

curl -X POST https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image \
  -H "Authorization: Bearer $AURAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "prompt": "…", "aspect_ratio": "16:9", "resolution": 1024 }'

You get 200 with the queue envelope straight away. The connection closing does not affect the job. Quickstart shows the whole body; the fields that matter here are request_id, status, queue_position (how many are ahead while the platform is full), credits_charged, credits_quoted, expected_seconds, and the three URLs. A job that waits in the queue is charged when it starts, not when it is submitted: until then its credits_charged is 0, and credits_quoted is what it will be charged.

Two options travel as query parameters on the submit: ?webhook=<url> (or ?fal_webhook=) asks for a signed delivery when the job succeeds, and ?wait=<seconds> holds the request open on the queue host for up to that long. The synchronous host does the second by default.

#Polling

curl "https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image/requests/photo_docs-mtpb1vnh/status?logs=1" \
  -H "Authorization: Bearer $AURAY_API_KEY"
200 OK, while it runs
{
  "status": "IN_PROGRESS",
  "request_id": "photo_docs-mtpb1vnh",
  "job_id": "photo_docs-mtpb1vnh",
  "logs": [],
  "metrics": {},
  "status_url": "…", "response_url": "…", "cancel_url": "…"
}

metrics.inference_time appears once the runner has reported how long the work itself took. Video reports its phase as a log line while it runs.

The platform read is the same job from the account's side, and it tells you how long to wait:

curl https://api.auray.ai/v1/jobs/photo_docs-mtpb1vnh \
  -H "Authorization: Bearer $AURAY_API_KEY"
200 OK, settled
{
  "id": "photo_docs-mtpb1vnh",
  "job_id": "photo_docs-mtpb1vnh",
  "object": "job",
  "product": "photo",
  "model": "auray-ai/krea-2-turbo/text-to-image",
  "engine": "Krea 2 Turbo",
  "status": "succeeded",
  "settled": true,
  "provider_job_id": "f30c5f765770468097181c2bcb31fd4d",
  "credits_charged": 4,
  "credits_reserved": 4,
  "created_at": "2026-09-06T04:21:58.245391+00:00",
  "error": null,
  "error_code": null,
  "assets": [
    { "index": 0, "kind": "image", "key": "photo/krea-2-turbo/f30c5f765770468097181c2bcb31fd4d/0.png", "width": 1344, "height": 768, "bytes": 1065131 }
  ],
  "request_id": "fd85461a-706a-4c71-9d0d-5391319b442a"
}

While a job is open the read carries poll_after_seconds; honour it. Reads are rate-limited on their own bucket, and a 429 carries Retry-After. Both RateLimit-* and X-RateLimit-* headers say where you stand on every answer.

For anything slower than a few seconds, a webhook is better: you get told once, at the moment it settles, instead of asking repeatedly.

#Collecting the output

The job's assets name what was made. The signed URLs to download it come from one route, on the platform host, because signing them is metered against the egress bucket and a status poll is not. The bucket counts the signing, not the fetch: a signed link can be fetched again until it expires, and asking for the links again counts their size again:

curl https://api.auray.ai/v1/jobs/photo_docs-mtpb1vnh/assets \
  -H "Authorization: Bearer $AURAY_API_KEY"

Each asset comes back with filename, content_type, bytes and a url that is good for at most 7 days and never past the file itself — so for what the job has left; the answer's expires_at says when. Download what you intend to keep — treat the URL as a handle to fetch through, not as permanent storage. Ask GET /v1/jobs/{id}/assets again for fresh links at any time in the job's 7 days. Fetching assets needs assets:read.

A job is kept 7 days. After that its request and its results are deleted, and both GET /v1/jobs/{id} and its assets answer 404 not_found — fetch what you need before then. In the minutes between the end of its 7 days and the sweep that deletes it, its assets answer 410 expired with reason: retention_ended instead: no link is signed past the life of the file it points at.

#Cancelling

curl -X DELETE https://api.auray.ai/v1/jobs/photo_docs-mtpb1vnh \
  -H "Authorization: Bearer $AURAY_API_KEY"

The answer says what the job became, whichever way it went:

200 OK — asked to cancel a job that had already finished
{
  "id": "photo_docs-mtpb1vnh",
  "object": "job",
  "product": "photo",
  "status": "succeeded",
  "settled": true,
  "cancelled": false,
  "credits_charged": 4,
  "credits_reserved": 4,
  "request_id": "9caaaf7e-d181-4892-90b1-6a7a74032d9a"
}

The queue host's PUT …/cancel answers the protocol's three: 202 with CANCELLATION_REQUESTED, 400 already_completed, or 404.

#Money

A refused job is never charged. Anything caught before inference — a bad scope, a malformed body, no credits, a ceiling reached, an address that is not open yet — costs nothing.

A job that fails after starting is refunded automatically. You do not file anything and there is nothing to reconcile.

Every case, product by product — read from the same declaration the refunds are tested against:

CaseImagesMusicVideo3DCatalogue models
Refused before it startsNothing is charged.Nothing is charged.Nothing is charged.Nothing is charged.Nothing is charged.
Waiting for a slotNothing is charged until it is dispatched.Nothing is charged until it is dispatched.Nothing is charged until it is dispatched.Nothing is charged until it is dispatched.No queue: it runs or is refused.
FailedEverything comes back.Everything comes back.Everything comes back.Everything comes back.Everything comes back.
Partly withheld or shortWhat did not arrive comes back; if nothing arrived, everything does.What did not arrive comes back; if nothing arrived, everything does.What did not arrive comes back; if nothing arrived, everything does.What did not arrive comes back; if nothing arrived, everything does.What did not arrive comes back; if nothing arrived, everything does.
Stopped before it beganEverything comes back.Everything comes back.Everything comes back.Everything comes back until the service starts it; from then — which can be while it still reads queued — the first stage's start-up share is kept.A job cannot be stopped.
Stopped while runningEverything comes back.The base price is kept, and so is what already ran, rounded down; the rest comes back.What already ran is kept, rounded down; the rest comes back.What already ran is kept, rounded down; the rest comes back.A job cannot be stopped; it finishes and is charged for what it delivers.
Stopped respondingClosed 30 minutes after it started, and everything comes back.Closed 2 hours after it started, and everything comes back.Closed 210 minutes after it started, and everything comes back.Closed 2 hours after it started, and everything comes back.Closed 2 hours after it started, and everything comes back.

So the only credits you keep paying for are the ones that produced something.

A chat turn — in Messages, a round of Code or the AI Director, /v1/chat and /openai/v1 — is not a job, and settles by its own rules. A model another company runs keeps writing an answer after it stops reaching you, so on one a turn in Messages, Playground or the API's chat that you stop, or whose connection drops, after we sent it to the model is finished and kept, and charged as the finished answer: for its prompt and what it wrote, never more than the credits set aside for it. A round of Code or the AI Director stopped then, and a turn or a round one of our time limits cuts off — the time a turn is given, a silence longer than we wait for its next word, or the wait for its first word — is charged in full, for its prompt and the longest answer it allowed, never more than the credits set aside for it. A turn you stop before we sent it costs nothing on any model; one that fails for any other reason is charged as any answer is, for its prompt and what reached you, and nothing if nothing did:

CaseOur own chat modelsChat models another company runs
Refused before it writes anythingNothing is charged.Only reading what the turn sent the model is charged, at its input rate; no per-turn credit.
FailedIf nothing reached you, everything comes back; otherwise what reached you is kept, with its prompt.If nothing reached you, everything comes back; otherwise what reached you is kept, with its prompt.
Stopped by you before we sent it to the modelEverything comes back: nothing had reached the model.Everything comes back: nothing had reached the model.
Stopped by you, or its connection lost, after we sent it to the model — a turn in Messages, Playground or the API's chatWhat already reached you is kept, with its prompt; if nothing had, everything comes back.The answer is finished and kept, and charged as the finished answer: reading what the turn sent the model and what the answer wrote, never more than the credits set aside when it began.
Stopped by you, or its connection lost, after we sent it to the model — a round in Code or the AI DirectorWhat already reached you is kept, with its prompt; if nothing had, everything comes back.Charged in full: reading what the round sent the model and the longest answer the round allowed, however much of it had reached you; never more than the credits set aside when it began.
Cut off at one of our time limits — the turn's, a silence, or the wait for its first wordWhat already reached you is kept, with its prompt; if nothing had, everything comes back.Charged in full: reading what the turn sent the model and the longest answer the turn — in Code and the AI Director, the round — allowed, however much of it had reached you; never more than the credits set aside when it began.

#Next

Start typing to search every page of the documentation.

Up arrow Down arrow to moveEnter to openEscape to close