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

# Auray API

Everything the Auray desktop can generate, your code can generate too — through the same account, the same credits and the same library. A job you start from a script appears in your Photos, Music or 3D app exactly like one you started by clicking.

One surface, at `https://api.auray.ai/v1`, authenticated by a bearer key you mint in the desktop. There is no SDK: the routes are JSON over HTTPS, and the machine-readable contract is at [`/v1/openapi.json`](https://api.auray.ai/v1/openapi.json) if you would rather generate a client than write one.

{% hint style="danger" %}
**The API is switched off on this deployment.** `API_V1_ENABLED` is unset, and that check runs before your key is parsed, so every `/v1` route answers `503 api_disabled` and a valid key looks exactly like an invented one. There is no key that gets past it and nothing to fix on your side.

The spec is the exception: it is a static file, so it is served whether or not the surface is open. Everything else on this site describes the code that will answer when the switch is flipped.
{% endhint %}

## What it generates

| Route             | What comes back                        | Bounds                                                                                      |
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
| `POST /v1/photo`  | PNG images                             | 1024 or 2048 px, up to 4 per job, seven aspect ratios                                       |
| `POST /v1/music`  | A WAV track                            | 10 to 300 seconds, instrumental or sung                                                     |
| `POST /v1/video`  | An MP4 clip                            | 4 to 15 seconds, tier `fast` or `pro`                                                       |
| `POST /v1/threed` | A GLB model, FBX from the rigged tiers | Built from an image you upload first; five tiers, `geometry` through `controlled-character` |
| `POST /v1/chat`   | Text, from `qwen` or `kimi`            | Answered in the same call, optionally as Server-Sent Events                                 |

The first four queue a job and hand back an id — `<product>_<idempotency_key>`, so `photo_nightly-482` — and you collect the result one of three ways: wait in the same call with `?wait=`, poll `GET /v1/jobs/{id}`, or register a webhook. They cost the same; pick whichever suits the shape of your program. Chat is the exception, and it is not queued at all: the answer is the response body.

What your plan allows differs by product — video duration, music duration and lyrics, 3D tiers, photo resolution. Those ceilings are read from the database on every request, so a selection made before a downgrade landed is still refused server-side.

## Your first call

{% stepper %}
{% step %}

### Mint a key

**Settings → API Keys**, in the desktop. It is shown once. We keep only a one-way hash, so a lost key cannot be recovered by anyone, including us.

A key is 71 characters: `auray_sk_`, a twelve-character public id, an underscore, then the secret. Expiry is required and there is no permanent option — 30, 90, 180 or 365 days.

A key minted without naming scopes gets `jobs:read` and `assets:read`, the two that cannot spend anything. Name the write scope you actually need: `photo:write`, `music:write`, `video:write`, `threed:write`, `chat:write`, `webhooks:write`.
{% endstep %}

{% step %}

### Prove the key works

`GET /v1/me` costs no credits, has no plan gate, no content filter and no wait. When it works, the only thing it proves is that your key works — which is exactly what you need to know two minutes after minting one.

{% tabs %}
{% tab title="curl" %}

```bash
curl https://api.auray.ai/v1/me \
  -H "Authorization: Bearer $AURA_API_KEY"
```

{% endtab %}

{% tab title="Node" %}

```javascript
const res = await fetch("https://api.auray.ai/v1/me", {
  headers: { Authorization: `Bearer ${process.env.AURA_API_KEY}` },
});
console.log(res.status, await res.json());
```

{% endtab %}

{% tab title="Python" %}

```python
import os, requests

r = requests.get(
    "https://api.auray.ai/v1/me",
    headers={"Authorization": f"Bearer {os.environ['AURA_API_KEY']}"},
)
print(r.status_code, r.json())
```

{% endtab %}
{% endtabs %}

```json
{
  "object": "account",
  "id": "fa7dda30-…",
  "plan": "pro",
  "credits": { "balance": 479, "monthly_allowance": 600, "period": "2026-08" },
  "key": {
    "id": "RsLuoyBOjfLN",
    "scopes": ["jobs:read", "photo:write"],
    "credit_ceiling": 600,
    "credits_spent_period": 94
  }
}
```

`credit_ceiling` is what this one key may spend this month, which is not the same as the wallet. It is set at mint to your plan's monthly grant, so a key can spend everything the plan gives and nothing more. On free it is 0, which is the honest number.
{% endstep %}

{% step %}

### Start something

```bash
curl "https://api.auray.ai/v1/photo?wait=120" \
  -H "Authorization: Bearer $AURA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "a single ripe fig on a slate tile, north window light, 85mm macro",
    "aspect_ratio": "1:1",
    "resolution": 1024,
    "idempotency_key": "nightly-482"
  }'
```

If it settles inside your wait you get `200` with `settled: true` and the assets described. If the wait runs out you get `202` with `timed_out: true` and a `poll_after_seconds` — never a 408 or a 504, because a library that retries on those would resubmit and you would pay twice. Either way the job carries on.

Sending the same `idempotency_key` twice never renders twice: the second call answers `200` with `replayed: true` and `credits_charged: 0`.
{% endstep %}

{% step %}

### Fetch the files

The job response describes assets and never contains URLs — a URL in a polling response is a live credential in a log, and a dead link by the time a slow consumer reads it. Ask for them when you want them:

```bash
curl https://api.auray.ai/v1/jobs/photo_nightly-482/assets \
  -H "Authorization: Bearer $AURA_API_KEY"
```

The URLs are signed and live fifteen minutes. Fetch them directly; we do not proxy the bytes.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
Every one of those calls answers `503 api_disabled` today. Read them as the shape of the integration, not as something you can run this afternoon.
{% endhint %}

## Where things are

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td>Getting started, credits and plans, jobs, webhooks, and how to write a prompt each model responds to.</td><td></td></tr><tr><td><strong>API Reference</strong></td><td>Every route and parameter, and all 95 error codes with what caused each one and what to do about it.</td><td></td></tr><tr><td><strong>Changelog</strong></td><td>What changed on the surface, newest first.</td><td></td></tr></tbody></table>

## Before you write any code

Six things that will otherwise be discovered from a refusal.

**There is no CORS, and that is not an oversight.** A secret key in a browser is a leaked key, and CORS is the only thing that makes putting one there convenient. No `Access-Control-*` header is ever sent, and a request arriving with an `Origin` header is refused with `403 browser_origin_refused` whatever key it carries. Some HTTP clients set `Origin` outside a browser too; remove it. Call this from a server.

**A free key cannot generate.** The free plan includes no monthly credits, so its `generate` bucket has a capacity of zero. That is a `403 plan_has_no_api_generate` rather than a `429`, because a bucket of zero never refills into one and retrying can only ever fail again. A free key can still list jobs and fetch assets from work you created before.

**3D runs one API job at a time, worldwide.** There are four GPU containers for the entire platform and the desktop keeps three. That is a slow API rather than a broken one. Photo and music each have one API lane plus a floating seat.

**Video cannot be waited on.** `?wait=` is refused outright with `400 wait_unsupported`; its cheapest clip takes longer than the whole request budget, and quietly ignoring the parameter would leave you reading an unfinished job as a finished one. Poll it, or register a webhook.

**`upscale_2k` is refused on every plan, Ultimate included**, with `403 upscale_not_in_plan`. The 2K pass calls a hosted service this deployment has no key for, so it is switched off rather than half-working — and refusing at validation is cheaper for you than a charge, a failed submit and a refund.

**Video has two tiers, `fast` and `pro`.** A third, `standard`, was retired: it ran the same 50 steps at the same quality as `pro` on slower hardware, so it was 3.2x slower, cost 15% more to run and charged 14% more for identical output. If a generated client still offers it, its spec is stale and the call will be rejected by validation.

{% hint style="warning" %}
Billing is in test mode. Checkout and cancellation work, but Stripe accepts only test cards and nothing is really charged. Credits granted and spent are real numbers against real GPU time; the money behind them is not moving yet.
{% endhint %}

***

Not writing code? [help.auray.ai](https://help.auray.ai) covers the desktop apps — Photos, Music, Video, 3D and the rest — with no keys or `curl` in sight.


---

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