> This is the Markdown version of the Azimo developer docs page "API reference", for use by an AI assistant.
> - Azimo (https://azimo.ai) is an AI business-video production service: a brief and a few files in, a QA-checked commercial video out. It offers an asynchronous REST API, Python and TypeScript SDKs, the azimo command line and an MCP server.
> - Base URL: https://azimo.ai
> - Auth: every /v1 request sends `Authorization: Bearer <API key>` (create a key at https://azimo.ai/console#account); `X-Project-ID` selects a project, the default project otherwise.
> - This page: https://azimo.ai/en/developers/api
> - Prices are not published: contact sales@azimo.ai.

# API reference

Parameters, request body, response fields and a curl example for every endpoint, generated from the live API definition.

## Basics

- **Base URL**: `https://azimo.ai`
- **Authentication**: Every `/v1` request sends `Authorization: Bearer <API key>`. Create a key in [your account](https://azimo.ai/console#account); the raw key is shown only once.
- **Projects**: `X-Project-ID` selects a project; the default project is used when it is omitted.
- **Idempotency**: Send an `Idempotency-Key` with requests that create something and reuse it when retrying; the same value with a different request returns 409.
- **Asynchronous jobs**: `POST /v1/videos` returns 202 and a video id; poll `GET /v1/videos/{job_id}` or receive a webhook. `needs_input` means something is missing, not that the video is done.
- **Pagination**: Lists return `data`, `next_cursor` and `has_more`; pass `next_cursor` as `cursor` to get the next page.
- **Pricing**: The API never returns prices. For a quote, contact sales@azimo.ai.

### Errors

| Name | Type | Required | Description |
|---|---|---|---|
| `error` | object | yes |  |
| `error.code` | string | yes | Stable machine-readable code, e.g. unauthenticated, not_found, idempotency_conflict, validation_error, rate_limited, insufficient_balance, budget_exceeded, contact_sales, internal_error |
| `error.message` | string | yes |  |
| `error.details` | any |  | Structured context: validation errors, unmet workflow requirements, or null |
| `error.request_id` | string | yes | nullable |

- `401` Missing or invalid bearer key (`WWW-Authenticate: Bearer`)
- `402` Wallet balance or spending limit would be exceeded
- `403` Key role or project scope does not allow this; `contact_sales` for anything about pricing
- `404` Resource not found in this organization/project
- `409` State conflict, or Idempotency-Key reused with a different request
- `422` Validation error; `details` lists the failing fields or requirements
- `429` Hourly limit reached (`Retry-After` in seconds)
- `500` Unexpected failure; cite `request_id` when reporting it

## Account and capabilities

Who the current key is, and which workflows and video types are available.

### GET /v1/me — Me

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `key_id` | string | yes | Same as `id` |
| `organization_id` | string | yes |  |
| `project_id` | string | yes |  |
| `role` | string | yes | One of: `owner` `editor` `viewer` |
| `billing_mode` | string | yes | One of: `budget` `prepaid` |
| `wallet` | object | yes |  |
| `permissions` | map<string, boolean> | yes |  |

```bash
curl "https://azimo.ai/v1/me" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/workflows — Workflows

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `router` | object | yes |  |
| `workflows` | map<string, object> | yes |  |

```bash
curl "https://azimo.ai/v1/workflows" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/workflows/creative_video/skills — Video Skills

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<any> | yes |  |

```bash
curl "https://azimo.ai/v1/workflows/creative_video/skills" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/video-types — Video Types

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `lang` (query) | string |  | Display language, `zh` (default) or `en`; when absent the first `zh`/`en` entry of `Accept-Language` is used; length ≤ 16; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |
| `accept-language` (header) | string |  | nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].version` | any | yes |  |
| `data[].template` | any |  |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/video-types" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/playground — Playground

Requires an API key. Success: 200.

The workflow gallery: seven fixed entries, each with routing values, whether this account can start it now, and a runnable example.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `lang` (query) | string |  | Display language, `zh` (default) or `en`; when absent the first `zh`/`en` entry of `Accept-Language` is used; length ≤ 16; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |
| `accept-language` (header) | string |  | nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes | Stable catalogue id |
| `data[].name` | string | yes |  |
| `data[].summary` | string | yes | One benefit-oriented sentence |
| `data[].workflow` | string | yes | Backend routing value for `options.workflow`; null when the entry cannot be started yet; nullable |
| `data[].video_type` | string | yes | `options.video_type` for structured explainers, otherwise null; nullable |
| `data[].aspect` | string | yes | One of: `16:9` `9:16` `1:1` |
| `data[].duration` | integer | yes | Default `options.duration_max` in seconds |
| `data[].duration_range` | array<integer> | yes | [minimum, maximum] seconds the workflow accepts |
| `data[].needs_assets` | boolean | yes | Reference material (photos, product pictures) is strongly recommended |
| `data[].available` | boolean | yes |  |
| `data[].reason` | string | yes | Why `available` is false: coming_soon, not_configured (this environment) or contact_sales (not sold to this account); One of: `coming_soon` `not_configured` `contact_sales`; nullable |
| `data[].coming_soon` | boolean | yes |  |
| `data[].example` | object | yes |  |
| `data[].example.prompt` | string | yes | A ready-to-run creative brief for `POST /v1/videos` or `POST /v1/plans` |
| `data[].example.content_data` | map<string, string> | yes | Sample value for every input of the workflow's video type (`options.content_data`); empty when the workflow has none |

```bash
curl "https://azimo.ai/v1/playground" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Assets

Upload PDFs, slides and pictures; reference the returned asset ids in a plan.

### GET /v1/assets — List Assets

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].filename` | string | yes |  |
| `data[].bytes` | integer | yes |  |
| `data[].sha256` | string | yes |  |
| `data[].created_at` | number | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/assets" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/assets — Upload Asset

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (multipart/form-data)

| Name | Type | Required | Description |
|---|---|---|---|
| `file` | file | yes |  |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `filename` | string | yes |  |
| `bytes` | integer | yes |  |
| `sha256` | string | yes |  |
| `created_at` | number | yes |  |

```bash
curl -X POST "https://azimo.ai/v1/assets" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@deck.pdf"
```

### GET /v1/assets/{asset_id} — Get Asset

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `asset_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `filename` | string | yes |  |
| `bytes` | integer | yes |  |
| `sha256` | string | yes |  |
| `created_at` | number | yes |  |

```bash
curl "https://azimo.ai/v1/assets/<asset_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### DELETE /v1/assets/{asset_id} — Delete Asset

Requires an API key. Success: 200.

Remove an upload and its bytes. Jobs that already consumed it keep their own copies.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `asset_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/assets/<asset_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/assets/{asset_id}/file — Download Asset

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `asset_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

```bash
curl "https://azimo.ai/v1/assets/<asset_id>/file" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Plans

Plan before producing: workflow, specs, assets and structure are frozen and anything missing is reported.

### GET /v1/plans — List Plans

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].kind` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].version` | integer | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].updated_at` | number | yes |  |
| `data[].data` | object | yes |  |
| `data[].data.status` | string | yes | One of: `planning` `ready` `needs_input` `unavailable` |
| `data[].data.request` | object | yes |  |
| `data[].data.assets` | array<object> |  |  |
| `data[].data.routing` | object |  | nullable |
| `data[].data.requirements` | array<object> |  |  |
| `data[].data.reason` | string |  | nullable |
| `data[].data.job_id` | string |  | The production this plan started; a plan starts at most one; nullable |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/plans" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/plans — Create Plan

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `prompt` | string |  | length ≤ 4000 |
| `assets` | array<string> |  | up to 30 items |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `webhook_url` | string |  | length ≤ 2000; nullable |
| `plan_id` | string |  | nullable |
| `template_id` | string |  | nullable |
| `brand_id` | string |  | nullable |
| `variables` | map<string, string> |  |  |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |
| `data.status` | string | yes | One of: `planning` `ready` `needs_input` `unavailable` |
| `data.request` | object | yes |  |
| `data.assets` | array<object> |  |  |
| `data.routing` | object |  | nullable |
| `data.requirements` | array<object> |  |  |
| `data.reason` | string |  | nullable |
| `data.job_id` | string |  | The production this plan started; a plan starts at most one; nullable |

```bash
curl -X POST "https://azimo.ai/v1/plans" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "<prompt>", "assets": ["<asset id>"]}'
```

### GET /v1/plans/{plan_id} — Get Plan

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `plan_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |
| `data.status` | string | yes | One of: `planning` `ready` `needs_input` `unavailable` |
| `data.request` | object | yes |  |
| `data.assets` | array<object> |  |  |
| `data.routing` | object |  | nullable |
| `data.requirements` | array<object> |  |  |
| `data.reason` | string |  | nullable |
| `data.job_id` | string |  | The production this plan started; a plan starts at most one; nullable |

```bash
curl "https://azimo.ai/v1/plans/<plan_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/route — Preview Route

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `prompt` | string |  | length ≤ 4000 |
| `assets` | array<string> |  | up to 30 items |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `webhook_url` | string |  | length ≤ 2000; nullable |
| `plan_id` | string |  | nullable |
| `template_id` | string |  | nullable |
| `brand_id` | string |  | nullable |
| `variables` | map<string, string> |  |  |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `plan_id` | string | yes |  |
| `workflow` | string |  | nullable |
| `requirements` | array<object> |  |  |

```bash
curl -X POST "https://azimo.ai/v1/route" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "<prompt>", "assets": ["<asset id>"]}'
```

## Videos

Produce a video from a confirmed plan, follow its progress, download it, resume, revise or cancel it.

### GET /v1/videos — List Videos

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `status` (query) | string |  | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled`; nullable |
| `batch_id` (query) | string |  | length ≤ 32; nullable |
| `created_after` (query) | number |  | range ≥ 0; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].batch_id` | string |  | nullable |
| `data[].parent_id` | string |  | nullable |
| `data[].status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `data[].stage` | string | yes | nullable |
| `data[].progress` | integer | yes | nullable |
| `data[].error` | string |  | nullable |
| `data[].created_at` | number | yes | nullable |
| `data[].updated_at` | number |  | nullable |
| `data[].started_at` | number |  | nullable |
| `data[].finished_at` | number |  | nullable |
| `data[].deleted_at` | number |  | nullable |
| `data[].recovery` | object | yes |  |
| `data[].recovery.attempt` | integer | yes |  |
| `data[].recovery.max_attempts` | integer | yes |  |
| `data[].recovery.retry_at` | number |  | nullable |
| `data[].result` | object |  | nullable |
| `data[].result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `data[].result.duration` | number |  | nullable |
| `data[].result.shot_count` | integer |  | nullable |
| `data[].result.visual_mix` | any |  |  |
| `data[].result.qa` | any |  |  |
| `data[].result.brief` | any |  |  |
| `data[].result.workflow` | string |  | nullable |
| `data[].result.routing` | any |  |  |
| `data[].result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `data[].result.native_project` | any |  |  |
| `data[].requested_workflow` | string | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/videos" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/videos — Create Video

Requires an API key. Success: 202.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `prompt` | string |  | length ≤ 4000 |
| `assets` | array<string> |  | up to 30 items |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `webhook_url` | string |  | length ≤ 2000; nullable |
| `plan_id` | string |  | nullable |
| `template_id` | string |  | nullable |
| `brand_id` | string |  | nullable |
| `variables` | map<string, string> |  |  |

**Response 202**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `batch_id` | string |  | nullable |
| `parent_id` | string |  | nullable |
| `status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `stage` | string | yes | nullable |
| `progress` | integer | yes | nullable |
| `error` | string |  | nullable |
| `created_at` | number | yes | nullable |
| `updated_at` | number |  | nullable |
| `started_at` | number |  | nullable |
| `finished_at` | number |  | nullable |
| `deleted_at` | number |  | nullable |
| `recovery` | object | yes |  |
| `recovery.attempt` | integer | yes |  |
| `recovery.max_attempts` | integer | yes |  |
| `recovery.retry_at` | number |  | nullable |
| `result` | object |  | nullable |
| `result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `result.duration` | number |  | nullable |
| `result.shot_count` | integer |  | nullable |
| `result.visual_mix` | any |  |  |
| `result.qa` | any |  |  |
| `result.brief` | any |  |  |
| `result.workflow` | string |  | nullable |
| `result.routing` | any |  |  |
| `result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `result.native_project` | any |  |  |
| `requested_workflow` | string | yes |  |
| `webhook_signing_secret` | string |  | Only present when the request set `webhook_url`: the project secret that signs that per-job callback (`Vidgen-Signature`). Store it; verify deliveries with it; nullable |

```bash
curl -X POST "https://azimo.ai/v1/videos" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "<prompt>", "assets": ["<asset id>"]}'
```

### POST /v1/videos/{job_id}/resume — Resume

Requires an API key. Success: 202.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `message` | string |  | length ≤ 4000 |
| `questionnaire_id` | string |  | length ≤ 200; nullable |
| `answers` | array<object> |  | up to 30 items |
| `assets` | array<string> |  | up to 9 items |
| `approve_budget` | boolean |  | Approve the pending budget stop; the server computes the new limit; Default `false` |
| `credit_budget` | integer |  | Legacy: explicit new Vikoo credit budget for a credit budget stop; range 1–1000000; nullable |
| `budget_usd` | number |  | Legacy: explicit new total USD limit for a budget stop; range 0–100000; nullable |

**Response 202**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `batch_id` | string |  | nullable |
| `parent_id` | string |  | nullable |
| `status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `stage` | string | yes | nullable |
| `progress` | integer | yes | nullable |
| `error` | string |  | nullable |
| `created_at` | number | yes | nullable |
| `updated_at` | number |  | nullable |
| `started_at` | number |  | nullable |
| `finished_at` | number |  | nullable |
| `deleted_at` | number |  | nullable |
| `recovery` | object | yes |  |
| `recovery.attempt` | integer | yes |  |
| `recovery.max_attempts` | integer | yes |  |
| `recovery.retry_at` | number |  | nullable |
| `result` | object |  | nullable |
| `result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `result.duration` | number |  | nullable |
| `result.shot_count` | integer |  | nullable |
| `result.visual_mix` | any |  |  |
| `result.qa` | any |  |  |
| `result.brief` | any |  |  |
| `result.workflow` | string |  | nullable |
| `result.routing` | any |  |  |
| `result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `result.native_project` | any |  |  |
| `requested_workflow` | string | yes |  |

```bash
curl -X POST "https://azimo.ai/v1/videos/<job_id>/resume" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"message": "<message>", "questionnaire_id": "<questionnaire_id>"}'
```

### POST /v1/videos/{job_id}/revisions — Revise Video

Requires an API key. Success: 202.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `prompt` | string |  | length 10–4000; nullable |
| `assets` | array<string> |  | up to 30 items; nullable |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |

**Response 202**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `batch_id` | string |  | nullable |
| `parent_id` | string |  | nullable |
| `status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `stage` | string | yes | nullable |
| `progress` | integer | yes | nullable |
| `error` | string |  | nullable |
| `created_at` | number | yes | nullable |
| `updated_at` | number |  | nullable |
| `started_at` | number |  | nullable |
| `finished_at` | number |  | nullable |
| `deleted_at` | number |  | nullable |
| `recovery` | object | yes |  |
| `recovery.attempt` | integer | yes |  |
| `recovery.max_attempts` | integer | yes |  |
| `recovery.retry_at` | number |  | nullable |
| `result` | object |  | nullable |
| `result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `result.duration` | number |  | nullable |
| `result.shot_count` | integer |  | nullable |
| `result.visual_mix` | any |  |  |
| `result.qa` | any |  |  |
| `result.brief` | any |  |  |
| `result.workflow` | string |  | nullable |
| `result.routing` | any |  |  |
| `result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `result.native_project` | any |  |  |
| `requested_workflow` | string | yes |  |
| `webhook_signing_secret` | string |  | Only present when the request set `webhook_url`: the project secret that signs that per-job callback (`Vidgen-Signature`). Store it; verify deliveries with it; nullable |

```bash
curl -X POST "https://azimo.ai/v1/videos/<job_id>/revisions" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "<prompt>", "assets": ["<asset id>"]}'
```

### GET /v1/videos/{job_id} — Get Video

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `batch_id` | string |  | nullable |
| `parent_id` | string |  | nullable |
| `status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `stage` | string | yes | nullable |
| `progress` | integer | yes | nullable |
| `error` | string |  | nullable |
| `created_at` | number | yes | nullable |
| `updated_at` | number |  | nullable |
| `started_at` | number |  | nullable |
| `finished_at` | number |  | nullable |
| `deleted_at` | number |  | nullable |
| `recovery` | object | yes |  |
| `recovery.attempt` | integer | yes |  |
| `recovery.max_attempts` | integer | yes |  |
| `recovery.retry_at` | number |  | nullable |
| `result` | object |  | nullable |
| `result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `result.duration` | number |  | nullable |
| `result.shot_count` | integer |  | nullable |
| `result.visual_mix` | any |  |  |
| `result.qa` | any |  |  |
| `result.brief` | any |  |  |
| `result.workflow` | string |  | nullable |
| `result.routing` | any |  |  |
| `result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `result.native_project` | any |  |  |
| `requested_workflow` | string | yes |  |

```bash
curl "https://azimo.ai/v1/videos/<job_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### DELETE /v1/videos/{job_id} — Delete Video

Requires an API key. Success: 200.

Purge a finished job's films, uploads copies and diagnostics; the job record stays for usage history.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/videos/<job_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/videos/{job_id}/cancel — Cancel Video

Requires an API key. Success: 200.

queued / needs_input jobs cancel at once; a running job stops before its next paid step (`cancelling`).

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `status` | string | yes | One of: `cancelled` `cancelling` |

```bash
curl -X POST "https://azimo.ai/v1/videos/<job_id>/cancel" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

### GET /v1/videos/{job_id}/files/{name} — Get File

Signed link; no key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` (path) | string | yes |  |
| `name` (path) | string | yes |  |
| `expires` (query) | integer | yes |  |
| `sig` (query) | string | yes |  |

```bash
curl "https://azimo.ai/v1/videos/<job_id>/files/<name>?expires=<expires>&sig=<sig>"
```

## Batches

Submit several videos at once.

### GET /v1/batches — List Batches

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].created_at` | number | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/batches" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/batches — Create Batch

Requires an API key. Success: 202.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string |  | Default `Batch`; length 1–160 |
| `items` | array<object> | yes | up to 50 items |
| `items[].prompt` | string |  | length ≤ 4000 |
| `items[].assets` | array<string> |  | up to 30 items |
| `items[].options` | object |  |  |
| `items[].options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `items[].options.goal` | string |  | length ≤ 2000 |
| `items[].options.audience` | string |  | length ≤ 1000 |
| `items[].options.duration_max` | integer |  | Default `60`; range 5–180 |
| `items[].options.width` | integer |  | Default `1920`; range 640–3840 |
| `items[].options.height` | integer |  | Default `1080`; range 360–2160 |
| `items[].options.fps` | integer |  | Default `30`; range 24–60 |
| `items[].options.subtitles` | boolean |  | Default `false` |
| `items[].options.music` | boolean |  | Default `true` |
| `items[].options.skill_id` | string |  | length ≤ 200; nullable |
| `items[].options.credit_budget` | integer |  | range 1–1000000; nullable |
| `items[].options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `items[].options.content_data` | map<string, string> |  |  |
| `items[].options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `items[].options.brand` | object |  |  |
| `items[].options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `items[].options.photo_subject` | string |  | length ≤ 120 |
| `items[].options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `items[].webhook_url` | string |  | length ≤ 2000; nullable |
| `items[].plan_id` | string |  | nullable |
| `items[].template_id` | string |  | nullable |
| `items[].brand_id` | string |  | nullable |
| `items[].variables` | map<string, string> |  |  |

**Response 202**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `name` | string | yes |  |
| `jobs` | array<object> | yes |  |
| `jobs[].id` | string | yes |  |
| `jobs[].project_id` | string | yes | nullable |
| `jobs[].batch_id` | string |  | nullable |
| `jobs[].parent_id` | string |  | nullable |
| `jobs[].status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `jobs[].stage` | string | yes | nullable |
| `jobs[].progress` | integer | yes | nullable |
| `jobs[].error` | string |  | nullable |
| `jobs[].created_at` | number | yes | nullable |
| `jobs[].updated_at` | number |  | nullable |
| `jobs[].started_at` | number |  | nullable |
| `jobs[].finished_at` | number |  | nullable |
| `jobs[].deleted_at` | number |  | nullable |
| `jobs[].recovery` | object | yes |  |
| `jobs[].recovery.attempt` | integer | yes |  |
| `jobs[].recovery.max_attempts` | integer | yes |  |
| `jobs[].recovery.retry_at` | number |  | nullable |
| `jobs[].result` | object |  | nullable |
| `jobs[].result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `jobs[].result.duration` | number |  | nullable |
| `jobs[].result.shot_count` | integer |  | nullable |
| `jobs[].result.visual_mix` | any |  |  |
| `jobs[].result.qa` | any |  |  |
| `jobs[].result.brief` | any |  |  |
| `jobs[].result.workflow` | string |  | nullable |
| `jobs[].result.routing` | any |  |  |
| `jobs[].result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `jobs[].result.native_project` | any |  |  |
| `jobs[].requested_workflow` | string | yes |  |
| `data` | array<object> | yes | Alias of `jobs`, kept for v0.3 clients |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].batch_id` | string |  | nullable |
| `data[].parent_id` | string |  | nullable |
| `data[].status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `data[].stage` | string | yes | nullable |
| `data[].progress` | integer | yes | nullable |
| `data[].error` | string |  | nullable |
| `data[].created_at` | number | yes | nullable |
| `data[].updated_at` | number |  | nullable |
| `data[].started_at` | number |  | nullable |
| `data[].finished_at` | number |  | nullable |
| `data[].deleted_at` | number |  | nullable |
| `data[].recovery` | object | yes |  |
| `data[].recovery.attempt` | integer | yes |  |
| `data[].recovery.max_attempts` | integer | yes |  |
| `data[].recovery.retry_at` | number |  | nullable |
| `data[].result` | object |  | nullable |
| `data[].result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `data[].result.duration` | number |  | nullable |
| `data[].result.shot_count` | integer |  | nullable |
| `data[].result.visual_mix` | any |  |  |
| `data[].result.qa` | any |  |  |
| `data[].result.brief` | any |  |  |
| `data[].result.workflow` | string |  | nullable |
| `data[].result.routing` | any |  |  |
| `data[].result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `data[].result.native_project` | any |  |  |
| `data[].requested_workflow` | string | yes |  |
| `counts` | map<string, integer> | yes |  |
| `webhook_signing_secret` | string |  | Set when an item carried `webhook_url`: the project secret that signs those per-job callbacks (`Vidgen-Signature`). Store it; verify deliveries with it; nullable |

```bash
curl -X POST "https://azimo.ai/v1/batches" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"items": [{}]}'
```

### GET /v1/batches/{batch_id} — Get Batch

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `batch_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `name` | string | yes |  |
| `jobs` | array<object> | yes |  |
| `jobs[].id` | string | yes |  |
| `jobs[].project_id` | string | yes | nullable |
| `jobs[].batch_id` | string |  | nullable |
| `jobs[].parent_id` | string |  | nullable |
| `jobs[].status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `jobs[].stage` | string | yes | nullable |
| `jobs[].progress` | integer | yes | nullable |
| `jobs[].error` | string |  | nullable |
| `jobs[].created_at` | number | yes | nullable |
| `jobs[].updated_at` | number |  | nullable |
| `jobs[].started_at` | number |  | nullable |
| `jobs[].finished_at` | number |  | nullable |
| `jobs[].deleted_at` | number |  | nullable |
| `jobs[].recovery` | object | yes |  |
| `jobs[].recovery.attempt` | integer | yes |  |
| `jobs[].recovery.max_attempts` | integer | yes |  |
| `jobs[].recovery.retry_at` | number |  | nullable |
| `jobs[].result` | object |  | nullable |
| `jobs[].result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `jobs[].result.duration` | number |  | nullable |
| `jobs[].result.shot_count` | integer |  | nullable |
| `jobs[].result.visual_mix` | any |  |  |
| `jobs[].result.qa` | any |  |  |
| `jobs[].result.brief` | any |  |  |
| `jobs[].result.workflow` | string |  | nullable |
| `jobs[].result.routing` | any |  |  |
| `jobs[].result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `jobs[].result.native_project` | any |  |  |
| `jobs[].requested_workflow` | string | yes |  |
| `data` | array<object> | yes | Alias of `jobs`, kept for v0.3 clients |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].batch_id` | string |  | nullable |
| `data[].parent_id` | string |  | nullable |
| `data[].status` | string | yes | One of: `queued` `running` `needs_input` `complete` `rejected` `failed` `cancelled` |
| `data[].stage` | string | yes | nullable |
| `data[].progress` | integer | yes | nullable |
| `data[].error` | string |  | nullable |
| `data[].created_at` | number | yes | nullable |
| `data[].updated_at` | number |  | nullable |
| `data[].started_at` | number |  | nullable |
| `data[].finished_at` | number |  | nullable |
| `data[].deleted_at` | number |  | nullable |
| `data[].recovery` | object | yes |  |
| `data[].recovery.attempt` | integer | yes |  |
| `data[].recovery.max_attempts` | integer | yes |  |
| `data[].recovery.retry_at` | number |  | nullable |
| `data[].result` | object |  | nullable |
| `data[].result.files` | map<string, string> |  | Signed download links; present on GET once complete/rejected; nullable |
| `data[].result.duration` | number |  | nullable |
| `data[].result.shot_count` | integer |  | nullable |
| `data[].result.visual_mix` | any |  |  |
| `data[].result.qa` | any |  |  |
| `data[].result.brief` | any |  |  |
| `data[].result.workflow` | string |  | nullable |
| `data[].result.routing` | any |  |  |
| `data[].result.input_required` | object |  | Why the job is `needs_input`: `code`, `actor`, `message` and what to send to /resume. A spending stop has `code: budget_approval` and `approvable: true` and carries no amounts; answer it with `POST /v1/videos/{id}/resume {"approve_budget": true}`; nullable |
| `data[].result.native_project` | any |  |  |
| `data[].requested_workflow` | string | yes |  |
| `counts` | map<string, integer> | yes |  |

```bash
curl "https://azimo.ai/v1/batches/<batch_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Events and webhooks

Instead of polling, receive status changes through signed webhooks or the event list.

### GET /v1/events — List Events

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `after` (query) | integer |  | Default `0`; range ≥ 0 |
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].sequence` | integer | yes |  |
| `data[].type` | string | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].api_version` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].data` | object | yes |  |
| `next_cursor` | integer | yes | Sequence number to pass as `after`; unchanged when the page is empty |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/events" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/webhooks — List Webhooks

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].kind` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].version` | integer | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].updated_at` | number | yes |  |
| `data[].data` | object | yes |  |
| `data[].data.name` | string | yes |  |
| `data[].data.url` | string | yes |  |
| `data[].data.active` | boolean |  | Default `true` |
| `data[].data.has_secret` | boolean | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/webhooks" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/webhooks — Create Webhook

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string |  | Default `Webhook`; length ≤ 160 |
| `url` | string | yes | length ≤ 2000 |
| `active` | boolean |  | Default `true` |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |
| `data.name` | string | yes |  |
| `data.url` | string | yes |  |
| `data.active` | boolean |  | Default `true` |
| `data.has_secret` | boolean | yes |  |
| `signing_secret` | string | yes | Shown at creation/rotation and for the same actor's idempotent retry within 24 hours; nullable |

```bash
curl -X POST "https://azimo.ai/v1/webhooks" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url": "<url>"}'
```

### POST /v1/webhooks/{webhook_id}/rotate-secret — Rotate Webhook Secret

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `webhook_id` (path) | string | yes |  |
| `expected_version` (query) | integer | yes | range ≥ 1 |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |
| `data.name` | string | yes |  |
| `data.url` | string | yes |  |
| `data.active` | boolean |  | Default `true` |
| `data.has_secret` | boolean | yes |  |
| `signing_secret` | string | yes | Shown at creation/rotation and for the same actor's idempotent retry within 24 hours; nullable |

```bash
curl -X POST "https://azimo.ai/v1/webhooks/<webhook_id>/rotate-secret?expected_version=<expected_version>" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

### PUT /v1/webhooks/{webhook_id} — Update Webhook

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `webhook_id` (path) | string | yes |  |
| `expected_version` (query) | integer | yes | range ≥ 1 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string |  | Default `Webhook`; length ≤ 160 |
| `url` | string | yes | length ≤ 2000 |
| `active` | boolean |  | Default `true` |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |
| `data.name` | string | yes |  |
| `data.url` | string | yes |  |
| `data.active` | boolean |  | Default `true` |
| `data.has_secret` | boolean | yes |  |

```bash
curl -X PUT "https://azimo.ai/v1/webhooks/<webhook_id>?expected_version=<expected_version>" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "<url>"}'
```

### DELETE /v1/webhooks/{webhook_id} — Delete Webhook

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `webhook_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/webhooks/<webhook_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/webhook-deliveries — List Deliveries

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].event_id` | string | yes |  |
| `data[].url` | string | yes |  |
| `data[].status` | string | yes |  |
| `data[].attempts` | integer | yes |  |
| `data[].last_error` | string | yes | nullable |
| `data[].delivered_at` | number | yes | nullable |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/webhook-deliveries" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/webhook-deliveries/{delivery_id}/replay — Replay

Requires an API key. Success: 202.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `delivery_id` (path) | string | yes |  |
| `webhook_id` (query) | string |  | length ≤ 32; nullable |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 202**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `status` | string | yes | One of: `pending` |

```bash
curl -X POST "https://azimo.ai/v1/webhook-deliveries/<delivery_id>/replay" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

## Brands and templates

Save brand kits and reusable templates, then reference them when producing.

### GET /v1/brands — Listing

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].kind` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].version` | integer | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].updated_at` | number | yes |  |
| `data[].data` | object | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/brands" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/brands — Create

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–120 |
| `slogan` | string |  | length ≤ 500 |
| `primary_color` | string |  | Default `#0F4C81` |
| `theme` | string |  | One of: `light` `dark`; Default `dark` |
| `assets` | array<string> |  | up to 30 items |
| `notes` | string |  | length ≤ 2000 |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl -X POST "https://azimo.ai/v1/brands" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>"}'
```

### GET /v1/brands/{resource_id} — Get

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `version` (query) | integer |  | range ≥ 1; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl "https://azimo.ai/v1/brands/<resource_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### PUT /v1/brands/{resource_id} — Revise

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `expected_version` (query) | integer | yes | range ≥ 1 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–120 |
| `slogan` | string |  | length ≤ 500 |
| `primary_color` | string |  | Default `#0F4C81` |
| `theme` | string |  | One of: `light` `dark`; Default `dark` |
| `assets` | array<string> |  | up to 30 items |
| `notes` | string |  | length ≤ 2000 |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl -X PUT "https://azimo.ai/v1/brands/<resource_id>?expected_version=<expected_version>" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>"}'
```

### DELETE /v1/brands/{resource_id} — Remove

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/brands/<resource_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/templates — Listing

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].kind` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].version` | integer | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].updated_at` | number | yes |  |
| `data[].data` | object | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/templates" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/templates — Create

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–160 |
| `prompt` | string | yes | Use $variable or ${variable} placeholders; length 10–4000 |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `assets` | array<string> |  | up to 30 items |
| `brand_id` | string |  | nullable |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl -X POST "https://azimo.ai/v1/templates" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>", "prompt": "<prompt>"}'
```

### GET /v1/templates/{resource_id} — Get

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `version` (query) | integer |  | range ≥ 1; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl "https://azimo.ai/v1/templates/<resource_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### PUT /v1/templates/{resource_id} — Revise

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `expected_version` (query) | integer | yes | range ≥ 1 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–160 |
| `prompt` | string | yes | Use $variable or ${variable} placeholders; length 10–4000 |
| `options` | object |  |  |
| `options.workflow` | string |  | One of: `auto` `explainer` `short_drama` `creative_video` `motion_explainer` `profile_card` `product_pitch`; Default `explainer` |
| `options.goal` | string |  | length ≤ 2000 |
| `options.audience` | string |  | length ≤ 1000 |
| `options.duration_max` | integer |  | Default `60`; range 5–180 |
| `options.width` | integer |  | Default `1920`; range 640–3840 |
| `options.height` | integer |  | Default `1080`; range 360–2160 |
| `options.fps` | integer |  | Default `30`; range 24–60 |
| `options.subtitles` | boolean |  | Default `false` |
| `options.music` | boolean |  | Default `true` |
| `options.skill_id` | string |  | length ≤ 200; nullable |
| `options.credit_budget` | integer |  | range 1–1000000; nullable |
| `options.video_type` | string |  | One of: `project` `product` `personal` `corporate`; nullable |
| `options.content_data` | map<string, string> |  |  |
| `options.style_preset` | string |  | One of: `cinematic_realism` `clean_3d` `product_studio`; Default `cinematic_realism` |
| `options.brand` | object |  |  |
| `options.motion_style` | string |  | One of: `mono` `photo`; Default `mono` |
| `options.photo_subject` | string |  | length ≤ 120 |
| `options.photo_license` | string |  | One of: `strict` `sharealike`; Default `strict` |
| `assets` | array<string> |  | up to 30 items |
| `brand_id` | string |  | nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `project_id` | string | yes | nullable |
| `kind` | string | yes |  |
| `name` | string | yes |  |
| `version` | integer | yes |  |
| `created_at` | number | yes |  |
| `updated_at` | number | yes |  |
| `data` | object | yes |  |

```bash
curl -X PUT "https://azimo.ai/v1/templates/<resource_id>?expected_version=<expected_version>" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>", "prompt": "<prompt>"}'
```

### DELETE /v1/templates/{resource_id} — Remove

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `resource_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/templates/<resource_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Projects, keys and audit

Manage projects and API keys, and read the change log.

### GET /v1/projects — List Projects

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].organization_id` | string | yes |  |
| `data[].name` | string | yes |  |
| `data[].created_at` | number | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/projects" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/projects — Create Project

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–120 |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `organization_id` | string | yes |  |
| `name` | string | yes |  |
| `created_at` | number | yes |  |

```bash
curl -X POST "https://azimo.ai/v1/projects" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>"}'
```

### DELETE /v1/projects/{project_id} — Delete Project

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `project_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `deleted` | boolean |  | Default `true` |

```bash
curl -X DELETE "https://azimo.ai/v1/projects/<project_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/api-keys — List Keys

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `cursor` (query) | string |  | length ≤ 256; nullable |
| `after` (query) | string |  | length ≤ 256; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].name` | string | yes | Label given at creation (also returned as `owner`) |
| `data[].owner` | string | yes |  |
| `data[].prefix` | string | yes | nullable |
| `data[].organization_id` | string | yes | nullable |
| `data[].project_id` | string |  | Set when the key is bound to one project; nullable |
| `data[].role` | string | yes | One of: `owner` `editor` `viewer` |
| `data[].active` | boolean | yes |  |
| `data[].created_at` | number | yes | nullable |
| `data[].last_used_at` | number |  | nullable |
| `data[].expires_at` | number |  | nullable |
| `data[].revoked_at` | number |  | nullable |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/api-keys" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/api-keys — Create Key

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | length 1–120 |
| `role` | string |  | One of: `owner` `editor` `viewer`; Default `editor` |
| `project_id` | string |  | nullable |
| `monthly_budget_usd` | number |  | range ≥ 0; nullable |
| `rate_per_hour` | integer |  | range ≥ 1; nullable |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `name` | string | yes | Label given at creation (also returned as `owner`) |
| `owner` | string | yes |  |
| `prefix` | string | yes | nullable |
| `organization_id` | string | yes | nullable |
| `project_id` | string |  | Set when the key is bound to one project; nullable |
| `role` | string | yes | One of: `owner` `editor` `viewer` |
| `active` | boolean | yes |  |
| `created_at` | number | yes | nullable |
| `last_used_at` | number |  | nullable |
| `expires_at` | number |  | nullable |
| `revoked_at` | number |  | nullable |
| `key` | string | yes | The bearer secret; shown once (an idempotent replay returns the same value) |

```bash
curl -X POST "https://azimo.ai/v1/api-keys" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "<name>"}'
```

### POST /v1/api-keys/{key_id}/rotate — Rotate Key

Requires an API key. Success: 200.

The old secret dies at once, or after `grace_seconds` so running fleets can switch without downtime.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `key_id` (path) | string | yes |  |
| `grace_seconds` (query) | integer |  | Default `0`; range 0–604800 |
| `Idempotency-Key` (header) | string |  | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `key` | string | yes |  |
| `previous_id` | string | yes |  |
| `previous_expires_in` | integer | yes | nullable |

```bash
curl -X POST "https://azimo.ai/v1/api-keys/<key_id>/rotate" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

### DELETE /v1/api-keys/{key_id} — Revoke Key

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `key_id` (path) | string | yes |  |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `active` | boolean |  | Default `false` |

```bash
curl -X DELETE "https://azimo.ai/v1/api-keys/<key_id>" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/audit — Audit Log

Requires an API key. Success: 200.

Identity and configuration changes for this organization (keys, projects, webhooks), oldest first from `after`.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `after` (query) | integer |  | Default `0`; range ≥ 0 |
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].sequence` | integer | yes |  |
| `data[].type` | string | yes |  |
| `data[].created_at` | number | yes |  |
| `data[].api_version` | string | yes |  |
| `data[].project_id` | string | yes | nullable |
| `data[].data` | object | yes |  |
| `next_cursor` | integer | yes | Sequence number to pass as `after`; unchanged when the page is empty |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/audit" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Wallet and usage

Balance, statement and usage, and online top-up.

### GET /v1/wallet — Wallet

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `balance_cents` | integer | yes |  |
| `held_cents` | integer | yes |  |
| `available_cents` | integer | yes |  |
| `low_balance_cents` | integer | yes | nullable |
| `currency` | string | yes |  |
| `organization_id` | string | yes |  |

```bash
curl "https://azimo.ai/v1/wallet" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### GET /v1/wallet/entries — Wallet Entries

Requires an API key. Success: 200.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `after` (query) | string |  | length ≤ 64; nullable |
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `data` | array<object> | yes |  |
| `data[].id` | string | yes |  |
| `data[].organization_id` | string | yes |  |
| `data[].kind` | string | yes |  |
| `data[].amount_cents` | integer | yes |  |
| `data[].hold_cents` | integer | yes |  |
| `data[].job_id` | string | yes | nullable |
| `data[].note` | string | yes | nullable |
| `data[].actor` | string | yes | nullable |
| `data[].created_at` | number | yes |  |
| `next_cursor` | string |  | Opaque; pass as `cursor` (or the `after` alias) for the next page; nullable |
| `has_more` | boolean |  | Default `false` |

```bash
curl "https://azimo.ai/v1/wallet/entries" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

### POST /v1/wallet/checkout — Checkout

Requires an API key. Success: 201.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` (header) | string | yes | A unique value you generate and keep; reuse it when retrying so the request never runs twice; length 1–128 |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Request body** (application/json)

| Name | Type | Required | Description |
|---|---|---|---|
| `amount_cents` | integer | yes | USD cents, $5–$10,000; range 500–1000000 |

**Response 201**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes |  |
| `url` | string | yes |  |
| `amount_cents` | integer | yes |  |
| `currency` | string |  | Default `USD` |
| `charge_currency` | string |  | Currency Stripe charges; the wallet is always credited amount_cents USD; Default `USD` |
| `charge_amount` | integer |  | Amount Stripe charges, in charge_currency minor units; Default `0` |

```bash
curl -X POST "https://azimo.ai/v1/wallet/checkout" \
  -H "Authorization: Bearer $AZIMO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents": 500}'
```

### GET /v1/usage — Usage

Requires an API key. Success: 200.

The amount billed for this project's paid calls in [from, to) (default: the last 30 days), plus job counts, grouped on request.
`group_by=job` pages by job id with `after`; `group_by=workflow` reads at most 5000 jobs' request options.

**Parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `from` (query) | number |  | range ≥ 0; nullable |
| `to` (query) | number |  | range ≥ 0; nullable |
| `group_by` (query) | string |  | Default `none` |
| `limit` (query) | integer |  | Default `50`; range 1–200 |
| `after` (query) | string |  | length ≤ 32; nullable |
| `X-Project-ID` (header) | string |  | The project to act on; the default project when omitted; nullable |

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `project_id` | string | yes |  |
| `range` | map<string, number> | yes |  |
| `jobs` | map<string, integer> | yes |  |
| `list_usd` | number | yes | Amount billed for the paid calls in the range |
| `group_by` | string | yes |  |
| `groups` | array<object> | yes | nullable |
| `next_cursor` | string | yes | nullable |

```bash
curl "https://azimo.ai/v1/usage" \
  -H "Authorization: Bearer $AZIMO_API_KEY"
```

## Service health

Health checks; no key needed.

### GET /healthz — Healthz

No authentication. Success: 200.

```bash
curl "https://azimo.ai/healthz"
```

### GET /readyz — Readyz

No authentication. Success: 200.

**Response 200**

| Name | Type | Required | Description |
|---|---|---|---|
| `ok` | boolean | yes |  |
| `db` | boolean | yes |  |
| `version` | string | yes |  |
| `queue_depth` | integer | yes |  |
| `running` | integer | yes |  |
| `worker_seen_at` | number |  | Latest job start or running-job heartbeat; nullable |
| `worker_alive` | boolean | yes | A worker sent a durable heartbeat within the last 5 minutes |

- `503` Service Unavailable

```bash
curl "https://azimo.ai/readyz"
```
