> 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/products.md).

# Products

Starting work.

## POST /photo

> Generate images

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"schemas":{"Product":{"type":"string","enum":["photo","music","video","threed","chat"]},"Status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"],"description":"Five words, whatever the product. Our internals disagree; you never see that."},"AssetDescriptor":{"type":"object","description":"Describes an asset. Never a URL — get those from /jobs/{id}/assets.","properties":{"index":{"type":"integer"},"kind":{"type":"string","enum":["image","audio","video","model"]},"key":{"type":["string","null"]},"bytes":{"type":["integer","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"stage":{"type":"string","description":"3D only."},"seconds":{"type":["number","null"],"description":"Audio and video assets: the real duration, once known."},"sample_rate":{"type":["integer","null"],"description":"Audio assets only."},"channels":{"type":["integer","null"],"description":"Audio assets only."}}},"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/photo":{"post":{"summary":"Generate images","operationId":"createPhotoJob","tags":["Products"],"parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":300},"description":"Hold the request open until the job settles. 200 with settled:true if it finishes, 202 with timed_out:true if the wait runs out — never 408 or 504, because a library that retries on those would resubmit and the caller would pay twice. Bounds the polling loop rather than the response time: the answer can arrive one upstream poll later than asked."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt","aspect_ratio","resolution"],"properties":{"prompt":{"type":"string","description":"Over 4,000 characters is refused with 400 prompt_too_long — but the limit is measured AFTER trimming, and `maxLength` here would be measured before, so a 4,000-character prompt with a trailing newline would be rejected by a generated client and accepted by us."},"aspect_ratio":{"type":"string","enum":["1:1","4:3","3:4","16:9","9:16","3:2","2:3"]},"resolution":{"type":"integer","enum":[1024,2048]},"num_images":{"type":"integer","minimum":1,"maximum":4,"description":"Four on Max and Ultimate, two on Pro; above your own plan's cap it is 403 images_not_in_plan. Each image is priced separately."},"style":{"type":["string","null"],"enum":["retroanime","sunsetblur","vintagetarot","rainywindow","darkbrush","dotmatrix","neondrip","kidsdrawing","softwatercolor",null],"description":"One of the nine styles, or null for none. Anything else is refused with 400 unknown_style rather than ignored."},"seed":{"type":"integer","description":"Reuse one to get the same image back. Out of range or the wrong type is IGNORED rather than refused — the request still runs, with the default. Declaring the bound here would make a generated client reject something this API accepts, so it is described instead."},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Your own id for this submission. Sending it twice never renders twice — the second call answers 200 with replayed:true and credits_charged:0. It becomes the second half of the job id. No colon, because a credit key is <product>:<user>:<this>."}}}}}},"responses":{"200":{"description":"Replayed, or settled after a wait.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"model":{"type":["string","null"],"description":"The model that rendered it. Null on a replay, which does not re-dispatch."},"attribution":{"type":["string","null"],"description":"The credit line the model's licence requires. Null on a replay."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"202":{"description":"Queued.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"model":{"type":["string","null"],"description":"The model that rendered it. Null on a replay, which does not re-dispatch."},"attribution":{"type":["string","null"],"description":"The credit line the model's licence requires. Null on a replay."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"description":"Ours, not yours. `credit_reserve_failed` happens before anything is charged, so there is nothing to reverse; `job_record_failed` happens after, and the charge IS reversed. Retry with a FRESH idempotency key either way — a reversal marks the debit rather than deleting it, so the old key answers 409 key_settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## POST /music

> Generate a song

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"schemas":{"Product":{"type":"string","enum":["photo","music","video","threed","chat"]},"Status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"],"description":"Five words, whatever the product. Our internals disagree; you never see that."},"AssetDescriptor":{"type":"object","description":"Describes an asset. Never a URL — get those from /jobs/{id}/assets.","properties":{"index":{"type":"integer"},"kind":{"type":"string","enum":["image","audio","video","model"]},"key":{"type":["string","null"]},"bytes":{"type":["integer","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"stage":{"type":"string","description":"3D only."},"seconds":{"type":["number","null"],"description":"Audio and video assets: the real duration, once known."},"sample_rate":{"type":["integer","null"],"description":"Audio assets only."},"channels":{"type":["integer","null"],"description":"Audio assets only."}}},"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/music":{"post":{"summary":"Generate a song","operationId":"createMusicJob","tags":["Products"],"parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":300},"description":"Hold the request open until the job settles. 200 with settled:true if it finishes, 202 with timed_out:true if the wait runs out — never 408 or 504, because a library that retries on those would resubmit and the caller would pay twice. Bounds the polling loop rather than the response time: the answer can arrive one upstream poll later than asked."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt","duration_seconds"],"properties":{"prompt":{"type":"string"},"duration_seconds":{"type":"integer","minimum":10,"maximum":300,"description":"Ten seconds to five minutes. Outside that range the request is refused with 400 duration_out_of_range rather than clamped. Your plan may cap it lower still, which is a separate 403 duration_not_in_plan."},"lyrics":{"type":"string","description":"Omit for an instrumental."},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Your own id for this submission. Sending it twice never renders twice — the second call answers 200 with replayed:true and credits_charged:0. It becomes the second half of the job id. No colon, because a credit key is <product>:<user>:<this>."},"seed":{"type":"integer","description":"Reuse one to get the same song back. Out of range or the wrong type is IGNORED rather than refused — the request still runs, with the default. Declaring the bound here would make a generated client reject something this API accepts, so it is described instead."}}}}}},"responses":{"200":{"description":"Replayed, or settled after a wait.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"attribution":{"type":["string","null"],"description":"The MiniMax credit line their licence requires you to display. Null on a replay."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"202":{"description":"Queued.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"attribution":{"type":["string","null"],"description":"The MiniMax credit line their licence requires you to display. Null on a replay."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"description":"Ours, not yours. `credit_reserve_failed` happens before anything is charged, so there is nothing to reverse; `job_record_failed` happens after, and the charge IS reversed. Retry with a FRESH idempotency key either way — a reversal marks the debit rather than deleting it, so the old key answers 409 key_settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## POST /video

> Generate a clip

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"schemas":{"Product":{"type":"string","enum":["photo","music","video","threed","chat"]},"Status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"],"description":"Five words, whatever the product. Our internals disagree; you never see that."},"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/video":{"post":{"summary":"Generate a clip","operationId":"createVideoJob","tags":["Products"],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string"},"duration_seconds":{"type":"integer","minimum":4,"maximum":15,"default":5,"description":"Four to fifteen seconds; outside that it is refused with 400 duration_out_of_range rather than clamped. Your plan may cap it lower, which is a separate 403."},"tier":{"type":"string","enum":["fast","pro"],"default":"pro","description":"A third tier called `standard` was retired: it rendered the same 50 steps as `pro` on slower hardware and cost more. `fast` is roughly a quarter of the price."},"aspect_ratio":{"type":"string","enum":["16:9","9:16","1:1","4:3","3:4","21:9"],"default":"16:9","description":"Only 16:9, 9:16 and 1:1 have warm containers; the other three pay a cold start."},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Your own id for this submission. Sending it twice never renders twice — but VIDEO ANSWERS 202 EITHER WAY, unlike photo, music and 3D, which answer 200 on a replay. The upstream dedupes on this key, so a replay is still a job that is still running; `replayed: true` in the body is what tells the two apart, not the status code. It becomes the second half of the job id. No colon, because a credit key is <product>:<user>:<this>."},"upscale_2k":{"type":"boolean","description":"REFUSED ON EVERY PLAN TODAY with 403 upscale_not_in_plan, including Ultimate. The 2K pass calls out to a hosted service this deployment has no key for, so it is switched off rather than half-working. Documented because it is parsed and refused, not because it is available — this is not an upgrade prompt. The name matters: the parser reads `upscale_2k` and has never read `upscale`."}},"description":"Validated strictly: an unrecognised tier or aspect ratio is refused rather than coerced, because a typo in a config should not bill for a render nobody asked for."}}}},"responses":{"202":{"description":"Queued. Video answers 202 even for a replay, because the upstream dedupes on the idempotency key and a replay is still a job that is still running.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"request_id":{"type":"string"},"tier":{"type":"string","enum":["fast","pro"],"description":"The tier that will render it."},"attribution":{"type":["string","null"],"description":"The credit line the model's licence requires you to carry. Null when the upstream job reports none."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"description":"`credit_reserve_failed` or `job_record_failed` — ours, not yours. Where a charge had already landed it is reversed; retry with a fresh idempotency key, since a reversal leaves the old one settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## POST /threed

> Turn an uploaded image into a 3D model

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"schemas":{"Product":{"type":"string","enum":["photo","music","video","threed","chat"]},"Status":{"type":"string","enum":["queued","running","succeeded","failed","cancelled"],"description":"Five words, whatever the product. Our internals disagree; you never see that."},"AssetDescriptor":{"type":"object","description":"Describes an asset. Never a URL — get those from /jobs/{id}/assets.","properties":{"index":{"type":"integer"},"kind":{"type":"string","enum":["image","audio","video","model"]},"key":{"type":["string","null"]},"bytes":{"type":["integer","null"]},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"stage":{"type":"string","description":"3D only."},"seconds":{"type":["number","null"],"description":"Audio and video assets: the real duration, once known."},"sample_rate":{"type":["integer","null"],"description":"Audio assets only."},"channels":{"type":["integer","null"],"description":"Audio assets only."}}},"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/threed":{"post":{"summary":"Turn an uploaded image into a 3D model","operationId":"createThreedJob","tags":["Products"],"parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":300},"description":"Hold the request open until the job settles. 200 with settled:true if it finishes, 202 with timed_out:true if the wait runs out — never 408 or 504, because a library that retries on those would resubmit and the caller would pay twice. Bounds the polling loop rather than the response time: the answer can arrive one upstream poll later than asked."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image_path","tier"],"properties":{"image_path":{"type":"string","description":"A path from POST /uploads. We never fetch a URL you give us."},"tier":{"type":"string","enum":["geometry","textured","rigged","controlled-object","controlled-character"],"description":"Required, with no default. Unlike video, the refusal body carries no list of valid tiers, so this enum is the only place the five values are written down."},"out_format":{"type":"string","enum":["glb","fbx"],"default":"glb","description":"`fbx` only means something on a tier that produces a skeleton — on `geometry`, `textured` or `controlled-object` it is refused with 400 fbx_needs_a_rigged_tier. Anything other than these two values is silently taken as `glb` rather than refused."},"control_type":{"type":"string","enum":["bbox","pose"],"description":"Coupled to the tier, and not optional in the way its position here suggests. `controlled-object` accepts `bbox` only; `controlled-character` accepts `bbox` or `pose`. On either, omitting it is 400 control_type_required. On `geometry`, `textured` or `rigged` it is refused: those tiers take no control at all."},"control_data":{"type":"array","description":"An array, never an object. Its shape follows `control_type`: for `bbox`, exactly three numbers in (0, 1] — an aspect ratio, renormalised server-side so the longest axis is 1.0; for `pose`, between 1 and 512 rows of exactly six numbers each. Required whenever `control_type` is.","oneOf":[{"title":"bbox","type":"array","items":{"type":"number","exclusiveMinimum":0,"maximum":1},"minItems":3,"maxItems":3},{"title":"pose","type":"array","items":{"type":"array","items":{"type":"number"},"minItems":6,"maxItems":6},"minItems":1,"maxItems":512}]},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Your own id for this submission. Sending it twice never renders twice — the second call answers 200 with replayed:true and credits_charged:0. It becomes the second half of the job id. No colon, because a credit key is <product>:<user>:<this>."},"num_inference_steps":{"type":"integer","default":50,"description":"More steps, more detail, more GPU time; 50 by default. Anything outside 1–100, or a value that is not a whole number, falls back to that default rather than being refused — the bounds are deliberately not declared, because a validator built from them would reject a request this API accepts. A numeric string is honoured too."},"seed":{"type":"integer","description":"Reuse one to get the same mesh back. Out of range or the wrong type is IGNORED rather than refused — the request still runs, with the default. Declaring the bound here would make a generated client reject something this API accepts, so it is described instead."},"source_name":{"type":"string","description":"A label carried through to the stored job, for your own bookkeeping."}}}}}},"responses":{"200":{"description":"Replayed, or settled after a wait.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"expected_artifacts":{"type":"array","items":{"type":"string"},"description":"The stages this tier will produce, in the order they land — `geometry`, `textured`, `rigged`. Always present; it is how you know what to expect before anything has."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"202":{"description":"Queued.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["job"]},"product":{"$ref":"#/components/schemas/Product"},"status":{"$ref":"#/components/schemas/Status"},"provider_job_id":{"type":["string","null"]},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"expected_seconds":{"type":["integer","null"]},"replayed":{"type":"boolean"},"settled":{"type":"boolean"},"waited":{"type":"boolean"},"timed_out":{"type":"boolean"},"poll_after_seconds":{"type":"integer"},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetDescriptor"}},"request_id":{"type":"string"},"expected_artifacts":{"type":"array","items":{"type":"string"},"description":"The stages this tier will produce, in the order they land — `geometry`, `textured`, `rigged`. Always present; it is how you know what to expect before anything has."},"error":{"type":["string","null"],"description":"Only on a `?wait=` that settled: why it failed, or null if it did not."},"wait_declined":{"type":"string","enum":["platform_busy","account_busy"],"description":"Present when `?wait=` was asked for and no wait seat was free. The job still ran; poll it."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"428":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"451":{"$ref":"#/components/responses/Error"},"500":{"description":"`profile_read_failed`, `credit_reserve_failed` or `job_record_failed` — ours, not yours. Where a charge had already landed it is reversed; retry with a fresh idempotency key, since a reversal leaves the old one settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## Ask a question

> Answers in the same call. stream:false returns the finished answer; stream:true writes Server-Sent Events carrying the same frames. conversation\_id is optional — omit it for a one-off and a thread is minted.

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"schemas":{"ChatTurn":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["chat.turn"]},"status":{"type":"string","enum":["succeeded","failed"]},"conversation_id":{"type":"string","format":"uuid"},"conversation_created":{"type":"boolean"},"message_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"reasoning":{"type":"string"},"usage":{"type":["object","null"]},"credits_reserved":{"type":"integer","description":"Chat reserves the worst case and settles down to what the model produced."},"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"},"product":{"type":"string","enum":["chat"],"description":"Always `chat`."},"model":{"type":"string","enum":["qwen","kimi"],"description":"The model that answered."},"effort":{"type":"string","enum":["low","medium","high","xhigh","max"],"description":"NOT THE VALUE YOU SENT. The request takes a ladder — `light`, `balanced`, `deep` — and this reports what that mapped to on the chosen model, which differs between them: qwen runs low/medium/xhigh, kimi runs low/high/max. Sending one of these back as `effort` is 400 unknown_effort."},"error":{"type":"string","description":"Present only when `status` is `failed`, and then it is the reason."}}},"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/chat":{"post":{"summary":"Ask a question","description":"Answers in the same call. stream:false returns the finished answer; stream:true writes Server-Sent Events carrying the same frames. conversation_id is optional — omit it for a one-off and a thread is minted.","operationId":"createChatTurn","tags":["Products"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["model","text"],"properties":{"model":{"type":"string","enum":["qwen","kimi"]},"effort":{"type":"string","enum":["light","balanced","deep"],"default":"balanced"},"text":{"type":"string"},"conversation_id":{"type":"string","format":"uuid"},"stream":{"type":"boolean","default":false},"include_reasoning":{"type":"boolean","default":false},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Your own id for this turn. CHAT DOES NOT REPLAY: unlike the four job products, sending the same key twice is refused — 409 key_settled once the first turn finished, 409 in_flight while it is still running — `key_settled` always carries the original `message_id`; `in_flight` carries it once the turn's row exists and null before that. Read that turn rather than re-sending. No colon, because a credit key is <product>:<user>:<this>."}}}}}},"responses":{"200":{"description":"The finished turn, or an SSE stream when stream:true.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatTurn"}},"text/event-stream":{"schema":{"type":"string","description":"One frame per event: open, reasoning, text, then either done or error, then settled, then `data: [DONE]`. The `error` frame carries a `code` and is the other terminal frame — a stream that ends without `done` did not simply stop."}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"description":"`conversation_not_found` — the conversation exists and belongs to somebody else. An id you have never used is NOT an error: chat mints the conversation at that id and reports `conversation_created: true`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"description":"`credit_reserve_failed` or `chat_start_failed` — ours, not yours. Where a charge had already landed it is reversed; retry with a fresh idempotency key, since a reversal leaves the old one settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## Start a thread

> Optional — POST /chat mints one when no conversation\_id is given.

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}}},"paths":{"/chat/conversations":{"post":{"summary":"Start a thread","description":"Optional — POST /chat mints one when no conversation_id is given.","operationId":"createConversation","tags":["Products"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","enum":["qwen","kimi"],"default":"qwen"},"title":{"type":"string","description":"Truncated to 200 characters rather than refused, and the shortened string is what comes back. `maxLength` is deliberately not declared: a validator built from it would reject a title this API accepts."}}}}}},"responses":{"201":{"description":"Created.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","enum":["conversation"]},"model":{"type":"string"},"title":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```

## Somewhere to PUT an image

> Returns a signed URL to upload to and the path to submit. Bytes never come through this API: the platform's request limit is smaller than the files 3D takes.

```json
{"openapi":"3.1.0","info":{"title":"Auray API","version":"1.0.0"},"tags":[{"name":"Products","description":"Starting work."}],"servers":[{"url":"https://api.auray.ai/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"auray_sk_","description":"Create one in Settings -> API Keys. Seventy-one characters: auray_sk_, a public twelve-character id, an underscore, and a secret we store only as a one-way hash. Every key expires; the maximum life is 366 days."}},"responses":{"Error":{"description":"Refused. See `error` for the machine-readable code.","headers":{"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error","message","request_id"],"properties":{"error":{"type":"string","description":"For your code."},"message":{"type":"string","description":"A sentence, for a human reading a log."},"docs":{"type":"string"},"request_id":{"type":"string","description":"Quote this if you write in."}}}}},"paths":{"/uploads":{"post":{"summary":"Somewhere to PUT an image","description":"Returns a signed URL to upload to and the path to submit. Bytes never come through this API: the platform's request limit is smaller than the files 3D takes.","operationId":"createUpload","tags":["Products"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["product","content_type","idempotency_key"],"properties":{"product":{"type":"string","enum":["threed","video"],"description":"Which bucket. It also decides which write scope is needed."},"content_type":{"type":"string","enum":["image/png","image/jpeg","image/webp"]},"idempotency_key":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,64}$","description":"Required here, unlike on a submit. It names the object, so a retry lands on the same one instead of failing as already-existing."}}}}}},"responses":{"200":{"description":"OK","headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Burst capacity."},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until refill."},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix seconds. The X- spelling is what client libraries read."},"X-Request-Id":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["upload"]},"upload_url":{"type":"string","format":"uri","description":"PUT the bytes here with Content-Type set to what you declared. Send no Auray credential: the signature in the URL is the authorisation."},"path":{"type":"string","description":"Send this back as image_path."},"bucket":{"type":"string"},"expires_at":{"type":"string","format":"date-time","description":"Two hours. Supabase fixes this and exposes no option."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}}}}
```


---

# 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/products.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.
