Docs

Working with the API

Addresses

A model's name is its URL, every address publishes its own contract, and the catalogue tells you which ones run today.

There is no model field in any request body. The model is the path.

POST https://queue.auray.run/auray-ai/krea-2-turbo/text-to-image
                             └──────┬─────┘ └──────┬─────┘ └───┬────┘
                              namespace        model        task

Two to six segments, namespace first, then the model and usually what it does. Where a maker versions a model, the version is part of the model's name and so of its address — bytedance/seedream/v5/pro/text-to-image, veed/lipsync/v2 beside veed/lipsync — and each version is an address of its own. What is never in the path is a version of this API: no v1 in front, and no per-model client library.

#Why it is shaped that way

You can construct a URL from a name you read anywhere. A model mentioned in a changelog entry is immediately callable — nothing to look up first.

Nothing in the body identifies the model, so a request body is only the parameters. Switching models means changing the URL, not rewriting the payload.

The same shape at every address. Whatever you learn calling one address applies to the next.

#What is out there

353 addresses, each with a published request shape you can build against and a page in the API reference. An address whose request shape is not published yet has no page, and a submit to it refuses with model_not_described rather than guess at your body.

Available right now

A model can be described and still unavailable — paused, or not open yet. Which ones run changes without a release; until one does, a submit to it is refused before anything is charged:

501 Not Implemented
{
  "error": "model_not_connected",
  "error_type": "NotImplementedError",
  "message": "hitem3d-hi3d-split is not available yet.",
  "retry": "never",
  "charged": "no",
  "docs": "https://auray.ai/docs/api/errors#model_not_connected",
  "request_id": "75c787b6-6628-49d3-a6a0-6342199dd0a2"
}

GET /v1/models reports connected, described or undescribed per address, and GET /v1/catalogue counts them without a key. Those are the only answers that are current, so do not cache either for long. Connected means it runs; it is not a promise that a model has been tested.

Two more answers say that a model you could run is stopped, and why. paused means a submit is refused with a 503 before anything is charged — the model or its product is paused — and the row carries the code that refusal would answer and, as reason, our note when there is one; model_disabled and vendor_disabled also send x-should-retry: false, because retrying will not help until it is resumed. retired means the address answers 410 model_deprecated, and the row names the successor to use. The chat slots (kind: chat, called at /v1/chat) and the embedding model (kind: embedding) are rows too, listed when you ask for them: ?kind= takes one kind or a comma-separated list, as in ?kind=chat,embedding. Left out, the listing is every model address.

A catalogue model is priced in credits when you submit it: the submit's answer carries credits_charged, and that is the whole of its price on every page and document here.

#Finding one

List the catalogue
curl "https://api.auray.ai/v1/models?limit=50" \
  -H "Authorization: Bearer $AURAY_API_KEY"
200 OK, abridged — the counts show the shape
{
  "object": "list",
  "data": [
    { "id": "auray-ai-krea-2-turbo-text-to-image", "address": "auray-ai/krea-2-turbo/text-to-image", "category": "text-to-image", "status": "connected", "openapi": "https://api.auray.ai/v1/models/auray-ai/krea-2-turbo/text-to-image/openapi.json" }
  ],
  "total": …,
  "described": …,
  "connected": …
}

total, described and connected count the whole catalogue whatever you filter by — ask the API for today's figures. ?category=image-to-3d filters by what a model does, ?status=connected by whether it runs, and ?q=hunyuan by a word in the address.

Each address also says how its jobs live and who may run it. lifecycle has whether a full platform queues it (queue), what a stop does (cancel), whether and how long a caller may wait (waitable, wait_ceiling_seconds), how often to poll, when a silent job is reaped and how long one usually takes. modality is what it makes, which is how a plan opens catalogue models — a model the plan leaves out is refused model_not_in_plan, unless a pass you hold for it covers the job — and licence, when it is not null, names the territories it may not be served in. The models by task index groups every address by what it makes.

#Reading what an address takes

Every address publishes two documents, and neither needs a key:

The contract, OpenAPI 3.1
curl https://api.auray.ai/v1/models/auray-ai/krea-2-turbo/text-to-image/openapi.json
The same thing, written for an agent
curl https://api.auray.ai/v1/models/auray-ai/krea-2-turbo/text-to-image/llms.txt

The OpenAPI document's components.schemas.Input is the request body: every field with its type, whether it is required, its default and its bounds — nested the way the model takes it, so a field written image_ref[].url in the model's own documentation is an array of objects here, not a flat key. info["x-auray-price"] is the price rule in credits (for a catalogue model: credits per unit of what it counts — seconds, megapixels, characters, or a text's bytes where unit is utf8_byte — band by band under rule, with the most a request may ask, and under rule.inputs what it is billed for sending or choosing: each input image, each byte of a prompt, an input video's or audio's seconds — and the output's, where it runs as long — or a reference's megapixels at the most they can come to, once a request where counts is any:; per second of output where counts is any: times the seconds, as a video whose settings select a higher rate or a resolution preset on the request whose when names it; an input or a preset with max_units 0 is refused before any charge, and one with no counts is a fee on the requests its when names; a floor: in counts is the least the factors before it are counted at — a frame's megapixels, so no second of video is quoted below the lowest rate — an inv: a speed counted the other way, never below one: half the speed, twice the count — and a spoken: text each character by the time it takes to say, as the rule's sentence weighs each script, a digit, a letter read by its name and a symbol; for a catalogue language model, where unit is million_tokens: prompt_credits and answer_credits a million tokens, a long_prompt pair past its above_tokens, and what the quote counts — prompt_bytes_per_token, image_prompt_tokens, document_prompt_tokens for a request that sends a document, default_answer_tokens when settings.maxTokens is left out and cache_write_multiplier for a cached prompt; and on every address metered, true where the price is a hold — the job is charged what it cost, never above the quote — and false where the quote is the charge), info["x-auray-scope"] the scope a new key is minted with, and info["x-auray-scopes"] every scope that also works — the older name a key may already hold. The whole index is one line per address at api.auray.ai/llms.txt.

A body is held to that document. A field it does not list, or a value outside its bounds, is refused before anything is charged (unknown_field, missing_field, bad_value). Our own auray-ai/ addresses are warning first: such a body is still served, with a Deprecation header and a Link to the error, and is refused the same way once enforcement starts, no sooner than 30 days after the warnings began. That includes num_outputs on the video address, which the published contract never offered and which is being withdrawn rather than published.

Fields that take a file are typed by what they take — an image, a video, a track, a mesh — and by how it travels: as a URL, inline as base64, or as a path from Uploads. A field the model only accepts as a file part is marked as such and refused with unsupported_encoding if you send it, because this API sends JSON.

#Namespaces

The first segment is who publishes the model — auray-ai/ for the models behind the apps. Every namespace is reached the same way, with the same auth and billing.

There is no practical difference, scope included: a key's scope names what it may make — image:write, 3d:write, and text:write for a model that answers in text — and reaches every address that makes it, whoever publishes it. (A key minted with the older scope names may carry external:write, which still reaches every address outside auray-ai/.) The URL shape, the job states, the webhook and the error vocabulary are identical too; each model's timing is its own lifecycle.

Our three chat slots have addresses under auray-ai/ too — auray-ai/glm-5-3-flash, auray-ai/deepseek-v4-1-flash and auray-ai/kimi-k3 — which POST /v1/chat and /openai/v1 accept beside the slot's plain name. They are conversations rather than jobs, so they have no queue address; GET /v1/models?kind=chat lists them by that plain name, which is what a chat turn prints as model.

#When an address moves

An address that has been replaced answers 410 Gone and names its successor rather than disappearing:

410 Gone
{
  "error": "model_deprecated",
  "error_type": "NotFoundError",
  "retry": "never",
  "charged": "no",
  "successor": "auray-ai/krea-2-turbo/text-to-image",
  "docs": "https://auray.ai/docs/api/errors#model_deprecated",
  "request_id": "54b4754a-3e17-4629-adb1-7d3f067e97d3"
}

So an integration written against a retired address fails loudly with the fix in the body, instead of silently getting different output from a model that quietly changed underneath it.

#Next

Start typing to search every page of the documentation.

Up arrow Down arrow to moveEnter to openEscape to close