> This is the Markdown version of the Azimo developer docs page "All developer docs", 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 ` (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 > - Prices are not published: contact sales@azimo.ai. --- # Azimo developer docs (complete) --- ## Quickstart Source: https://azimo.ai/en/developers/quickstart Go from an API key to your first submitted video in about five minutes: upload material, create a plan, start production, wait, and download the film. ### Plan first, then produce A plan is free: it checks your request and freezes it. Only starting production from a plan uses your account balance. So every film takes two calls. First `POST /v1/plans` and check that the plan is `ready`. When you are happy with it, `POST /v1/videos` to start production. ### Step 1: Get an API key 1. Sign in at [https://azimo.ai/console](https://azimo.ai/console). During the beta you need an invite code to register; email sales@azimo.ai to get one. 2. Open **Account → API Key** and create a key. It is shown only once, so copy it straight away. 3. Put it in an environment variable. Every example below uses it: ```bash export AZIMO_API_KEY="your-api-key" ``` Send it on every request as `Authorization: Bearer $AZIMO_API_KEY`. Keep it out of your source code and your repository. ### Step 2: Walk through it with curl **Upload a file (optional).** Accepted types: PDF, PPTX, TXT, MD, PNG, JPG, JPEG, WEBP, MP4, MOV, WEBM, MP3, WAV and M4A, up to 60 MB each. ```bash curl -s https://azimo.ai/v1/assets \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Idempotency-Key: upload-brief-001" \ -F "file=@brief.pdf" ``` The `id` in the response is the asset id, for example `"id": "744c8c43aa494843"`. **Create a plan.** In the `prompt`, say what the film is about, who will watch it and what they should do afterwards. ```bash curl -s https://azimo.ai/v1/plans \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: kettle-plan-001" \ -d '{ "prompt": "A 30-second product film for people who travel for work and stay in hotels: the Pliva Fold Kettle folds flat into a laptop bag, boils two cups at a time, and viewers should search for it in the official store.", "assets": ["744c8c43aa494843"], "options": {"workflow": "explainer", "duration_max": 30, "subtitles": true} }' ``` **Check the plan.** You can only produce a plan whose `data.status` is `ready`. If it is `needs_input`, `data.requirements` explains what is missing or unsupported; fix the request and create a new plan. ```json {"id": "0bb27ba1d9d84b1a", "data": {"status": "ready", "requirements": [], "request": {"prompt": "…", "options": {"…": "…"}}}} ``` **Start production.** Send only the plan id. This is the step that uses your balance. It returns `202` and a video id. ```bash curl -s https://azimo.ai/v1/videos \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: kettle-video-001" \ -d '{"plan_id": "0bb27ba1d9d84b1a"}' ``` **Wait for it.** Production usually takes several minutes. Checking every 10 seconds or so is plenty: ```bash curl -s https://azimo.ai/v1/videos/b6bf4689035d474f \ -H "Authorization: Bearer $AZIMO_API_KEY" ``` The `status` moves through `queued` and `running`, then settles on one of these: | Status | Meaning | What to do | | --- | --- | --- | | `complete` | The film passed its checks | Download it | | `needs_input` | Azimo needs an answer or your approval to continue | Read `result.input_required`; see [Advanced usage](https://azimo.ai/en/developers/advanced) | | `rejected` | The film did not pass the quality checks | Read `result.qa`, adjust, and produce again | | `failed` / `cancelled` | Production failed or was cancelled | Read `error` | **Download.** Once the video is `complete`, `result.files` holds signed download links: `film` is the MP4, and there may also be `srt` (subtitles) and `cover` (a still image). The links work without your key: ```bash curl -L -o film.mp4 "" ``` Links expire. If one has expired, fetch the video again to get fresh links. ### The same flow with the Python SDK You need Python 3.9 or later. See [CLI and MCP](https://azimo.ai/en/developers/cli-mcp) for installation: the `vidgen-sdk` package contains both the SDK and the `azimo` command. ```python import os from vidgen_sdk import Vidgen with Vidgen("https://azimo.ai", os.environ["AZIMO_API_KEY"]) as azimo: asset = azimo.upload_asset("brief.pdf", idempotency_key="upload-brief-001") plan = azimo.create_plan({ "prompt": "A 30-second product film for people who travel for work: the Pliva Fold Kettle " "folds flat, boils two cups, and viewers should search for it in the official store.", "assets": [asset["id"]], "options": {"workflow": "explainer", "duration_max": 30, "subtitles": True}, }, idempotency_key="kettle-plan-001") print(plan["data"]["status"], plan["data"]["requirements"]) if plan["data"]["status"] == "ready": video = azimo.create_video(plan_id=plan["id"], idempotency_key="kettle-video-001") video = azimo.wait(video["id"], timeout=3600) # also returns on needs_input if video["status"] == "complete": with open("film.mp4", "wb") as f: f.write(azimo.download_file(video["result"]["files"]["film"])) else: print(video["status"], video.get("result", {}).get("input_required")) ``` On any error the SDK raises `VidgenError`, which carries `status`, `code`, `message` and `request_id`. ### The same flow with the azimo command line ```bash echo "$AZIMO_API_KEY" | azimo login # checks the key, then saves it azimo assets upload brief.pdf # prints the asset id azimo plan --prompt "A 30-second product film for hotel travellers about the Pliva Fold Kettle..." \ --duration 30 --subtitles --asset 744c8c43aa494843 azimo create --plan 0bb27ba1d9d84b1a # starts production (uses your balance) azimo wait b6bf4689035d474f # waits; exit code 3 if it did not complete azimo download b6bf4689035d474f --name all # saves the film, subtitles and cover ``` `azimo plan` only creates a plan; it never starts production. See [CLI and MCP](https://azimo.ai/en/developers/cli-mcp) for more commands. ### Two good habits - **Send an idempotency key on every write, and keep it.** Put your own value in the `Idempotency-Key` header. If a request times out, retry with the same key and you get the original result instead of a second film. The flip side: submitting the same plan again with a new key produces another video. - **A timeout is not a failure.** Check the video's status before you submit anything again. ### Pricing and balance Production uses your account balance. For pricing, contact sales@azimo.ai. ### Next steps - [Workflows and video types](https://azimo.ai/en/developers/workflows): what each of the six film types needs and how to ask for it. - [CLI and MCP](https://azimo.ai/en/developers/cli-mcp): use Azimo from a terminal or an AI assistant. - [Advanced usage](https://azimo.ai/en/developers/advanced): webhooks, errors, rate limits, budget approval and revisions. - [API reference](https://azimo.ai/en/developers/api): every endpoint and field. --- ## Workflows and video types Source: https://azimo.ai/en/developers/workflows Azimo offers six ready-made workflows, each for a different kind of business video; this page explains what each one is for, what to prepare and how to write the smallest request that works. ### The catalogue at a glance | Workflow | id | Format | Length (default / range) | How to select it | | --- | --- | --- | --- | --- | | Project introduction | `project` | 16:9 | 60 s / 20–180 s | `workflow: explainer` + `video_type: project` | | Person card | `profile_card` | 9:16 | 60 s / 30–90 s | `workflow: profile_card` | | Product introduction | `product` | 16:9 | 30 s / 20–180 s | `workflow: explainer` + `video_type: product` | | Company introduction | `corporate` | 16:9 | 60 s / 20–180 s | `workflow: explainer` + `video_type: corporate` | | Short-drama ad | `short_drama` | 9:16 | 30 s / 5–150 s | `workflow: short_drama` | | E-commerce ad | `ecommerce_ad` | 9:16 | 30 s / 5–120 s | `workflow: creative_video` | Custom workflows are coming soon. `GET /v1/playground` (or `azimo playground`) returns the same catalogue. It also tells you whether your account can use each workflow right now (`available`, with a `reason` when it cannot) and gives a runnable example for each. Add `?lang=en` for English text. Every request below goes to `POST /v1/plans`. Once the plan is `ready`, start production with `POST /v1/videos`; see the [Quickstart](https://azimo.ai/en/developers/quickstart). Set the length with `options.duration_max` (seconds) and the format with `options.width` and `options.height`: 16:9 is 1920×1080, 9:16 is 1080×1920 and 1:1 is 1080×1080. ### Project introduction (project) **Use it to** explain a project to prospects or partners. The structure is fixed: pain point, solution, advantages, ending on one next step. **Provide** all seven fields in `options.content_data`; leaving one out returns a 422. They are `project_name`, `audience_role` (who it is for), `pain_scenario`, `solution_summary` (one-sentence definition), `solution_steps` (two or three steps), `advantages_with_evidence` (each advantage with its source) and `call_to_action`. Upload screenshots, pilot reports or similar material if you have them. ```json { "prompt": "Calm, clear, business tone.", "options": { "workflow": "explainer", "video_type": "project", "duration_max": 60, "content_data": { "project_name": "NorthLeaf Route", "audience_role": "Owners of grocery chains with 5 to 30 stores", "pain_scenario": "Suppliers deliver at different times each morning, the loading bay is blocked and produce reaches the shelves late.", "solution_summary": "A shared delivery-scheduling service that gives each area one delivery window.", "solution_steps": "1) Stores confirm tomorrow's order. 2) Orders are grouped by area with one window per route. 3) Drivers follow the combined route.", "advantages_with_evidence": "Fewer separate deliveries | source: pilot summary provided by the client | shorter waits at the bay.", "call_to_action": "Book a demo." } } } ``` ### Person card (profile_card) **Use it for** a vertical, interview-style clip about one person: their story, expertise and contact details, easy to share. **Provide** the person's story in `prompt`: name, role, what they have done, a client quote and how to get in touch. Upload a clear photo of the person; the first image is used as their photo, and without one the person on screen will not be them. TXT, MD and PDF files such as a CV are added to the story. The format must be 1080×1920. ```json { "prompt": "Zhou Yanzhi, founder of Spruce Woodworks. Twelve years as a furniture designer; builds custom furniture only from locally reclaimed timber. Client quote: two years on, the drawers of my desk still glide. Contact: book through the workshop front desk.", "assets": [""], "options": {"workflow": "profile_card", "duration_max": 60, "width": 1080, "height": 1920} } ``` If you want voice-over and footage without the person on screen, use the structured type `personal` instead (`workflow: explainer`); its fields are listed by `GET /v1/video-types`. ### Product introduction (product) **Use it to** show in about 30 seconds who a product is for, why it stands out and how it is used, then point to one clear way to buy or enquire. **Provide** seven fields: `product_name`, `audience_role` (who it is for), `usage_scenario`, `main_benefits` (up to three), `demonstration` (actions that can be shown), `evidence` (where each claim comes from) and `purchase_action`. Product photos and a spec sheet are strongly recommended; the product will look the way your pictures show it. ```json { "assets": [""], "options": { "workflow": "explainer", "video_type": "product", "duration_max": 30, "content_data": { "product_name": "Pliva Fold Kettle", "audience_role": "Business travellers who stay in hotels", "usage_scenario": "Late at night in a hotel room, wanting a hot drink before bed.", "main_benefits": "Folds flat into a laptop bag; boils two cups; the lid locks shut.", "demonstration": "Unfold, fill, switch on, pour into a cup, fold flat again.", "evidence": "Spec sheet and test footage provided by the client.", "purchase_action": "Search for Pliva Fold Kettle in the brand's official store." } } } ``` ### Company introduction (corporate) **Use it to** present your positioning, core business, capabilities and proof in about a minute, as an opener for partnership conversations. **Provide** seven fields: `company_name`, `positioning`, `audience_role`, `core_business`, `capabilities`, `evidence` and `cooperation_action`. The more concrete your case studies, certificates and site photos, the better. ```json { "options": { "workflow": "explainer", "video_type": "corporate", "duration_max": 60, "content_data": { "company_name": "Fernwick Packaging Co., Ltd.", "positioning": "Recyclable protective packaging for e-commerce and food brands.", "audience_role": "E-commerce and food brands with regular shipping volume", "core_business": "Moulded-fibre trays, mailer inserts and cushions made to fit each product.", "capabilities": "In-house structural design, a moulding line and a testing lab.", "evidence": "Case studies and drop-test reports from three customers, provided by the client.", "cooperation_action": "Send us your product dimensions and request a sample." } } } ``` ### Short-drama ad (short_drama) **Use it for** an ad told as a short drama, around 30 seconds, with a real story arc that carries your brand into the viewer's memory. **Provide** a `prompt` that names the characters, setting, conflict and turn, the tone, and the closing product shot and brand line. You can attach a script or brand material as TXT, PDF or PPTX. Formats: 16:9, 9:16 or 1:1. ```json { "prompt": "A 30-second ad-style micro-drama for a fictional brand, Warm Lamp Coffee. Late at night, a tired designer, Lin, finds a hand-drip coffee and a note from her flatmate: 'I checked tomorrow's slides for you. Go to sleep.' She smiles, sips, and the light turns warm. End on the product and the brand line.", "options": {"workflow": "short_drama", "duration_max": 30, "width": 1080, "height": 1920} } ``` ### E-commerce ad (ecommerce_ad) **Use it for** a vertical e-commerce ad that turns product details and everyday use into a reason to tap through to the listing. **Provide** product and packaging photos; they matter most. In the `prompt`, describe the product, the shots, the colours, the voice, and anything that must not be said (for example, no health claims). If the material is not enough, the video may stop at `needs_input` and ask you for more. Formats: 16:9, 9:16 or 1:1. ```json { "prompt": "A 30-second vertical e-commerce ad for Qingye Tea Crystals, in jasmine green tea and roasted oolong, individual stick packs, hot or cold. Show a macro of crystals dissolving in a glass and both packs side by side; gentle female voice-over. Use only what the pack photos show; no health claims.", "assets": [""], "options": {"workflow": "creative_video", "duration_max": 30, "width": 1080, "height": 1920, "subtitles": true} } ``` ### What we check before delivery Every video goes through automatic checks before it is delivered; the exact checks vary a little by workflow. The results are in the video's `result.qa`. - **Specs:** resolution, frame rate and length match the request, there is an audio track, and the whole file decodes cleanly. - **Structure:** for structured types, every section is present and in order, and sections that need evidence cite the material you gave. - **Content:** the narration is transcribed and compared with what the film should say, including numbers and units. - **Brand and text:** the brand name is pronounced correctly, and the end card's brand, contact details and call to action are complete and not cut off. - **Picture:** each shot is checked for quality, text-only frames included; landscape footage in a vertical film keeps the whole picture. - **Sound:** narration is complete, in sync with the picture, and the end card stays up long enough to read. - **No invention:** figures, career history, case studies and results without a source do not appear in the film. - If any check fails, the video ends as `rejected`, never as `complete`. ### How to write a good brief - Say three things in one sentence: what the film is about, who watches it, and what they should do next. - Describe a specific person and moment ("a business traveller in a hotel room late at night"), not "everyone". - Give at most three selling points, each with a source or a demonstration that can be filmed. - Only include numbers you can back up; if you have no data, describe what you actually do. - Write names, slogans and contact details exactly as they should appear; they are used verbatim. - Upload real product photos, portraits and documents; the film follows your material. - Say what must not appear, such as promotions, health claims or competitor names. - Keep the length within the workflow's range, and when a plan comes back `needs_input`, read its `requirements` first. For every field, see the [API reference](https://azimo.ai/en/developers/api). --- ## CLI and MCP Source: https://azimo.ai/en/developers/cli-mcp Make videos from your terminal with the `azimo` command, or connect Azimo over MCP so an AI assistant such as Claude or Cursor can upload, plan, produce and download for you. ### Install The command line, the MCP server and the Python SDK ship in one Python package, `vidgen-sdk`. It needs Python 3.9 or later and depends only on `requests`. The package is not on PyPI yet. During the beta, ask sales@azimo.ai for it, then install the folder you receive with pipx or pip: ```bash pipx install ./vidgen-sdk # or: pip install ./vidgen-sdk azimo version ``` ### Sign in Create a key at [https://azimo.ai/console](https://azimo.ai/console) under **Account → API Key**, then: ```bash azimo login # type the key (hidden); saved only if it works echo "$AZIMO_API_KEY" | azimo login # or pipe it in azimo whoami # shows your role, organization and project ``` The key is stored in `~/.config/azimo/config.json`, readable only by you. You can skip `login` and set `AZIMO_API_KEY` instead. To work in another project, pass `--project` or set `AZIMO_PROJECT`. ### The commands you will use A whole film, start to finish: ```bash azimo playground # see the ready-made workflows azimo assets upload brief.pdf logo.png # upload material, get asset ids azimo plan --prompt "A 30-second film for food-industry buyers on why our cold-chain delivery is more reliable" \ --duration 30 --ratio 9:16 --subtitles --asset 3f2a9c1b7d6e4a58 azimo create --plan 53f755f4a1b24c0e # start production (uses your balance) azimo wait 188f86a5c3d94e21 # wait until it finishes azimo download 188f86a5c3d94e21 --name all --dir ./out ``` For a structured type, use `--type` with one `--field` per input. `azimo types` lists the field names: ```bash azimo plan --type product --duration 30 \ --field product_name="Pliva Fold Kettle" --field audience_role="Business travellers" \ --field usage_scenario="A hotel room late at night" --field main_benefits="Folds flat; two cups; no spills" \ --field demonstration="Unfold, boil, pour, fold" --field evidence="Spec sheet from the client" \ --field purchase_action="Search for it in the official store" ``` | Command | What it does | | --- | --- | | `azimo playground` / `azimo types` | Ready-made workflows; structured types and their fields | | `azimo assets upload FILE...` | Upload material and print the asset ids | | `azimo plan ...` | Create a plan without starting production | | `azimo create --plan ID` | Start production from a plan | | `azimo run ...` | Create a plan, ask you on a terminal, then start (`--yes` skips the question; scripts are never asked) | | `azimo status ID` / `azimo wait ID` / `azimo list` | Show, wait for, or list videos | | `azimo download ID --name all` | Save the film, subtitles and cover | | `azimo resume ID --message "..."` / `--approve-budget` | Answer a `needs_input` video, or approve it to continue | | `azimo cancel ID` / `azimo balance` | Cancel a video; show your balance | Useful `azimo plan` flags: `--prompt` or `--prompt-file`, `--workflow`, `--type`, `--field`, `--duration` (seconds), `--ratio` (`16:9`, `9:16` or `1:1`), `--asset` (repeatable), `--subtitles` and `--no-music`. Every command accepts `--json` for machine-readable output. Exit codes: `0` success, `1` error, `2` usage error, `3` `wait` ended without a finished film, `4` missing or invalid key. Watch out: every `azimo create` run starts a new production, so running it twice on the same plan makes two videos. ### Connect an AI assistant (MCP) There are two ways to connect, with exactly the same tools: - **Local (recommended):** `azimo mcp` runs on your computer and can upload files straight from your disk. Install the package above first. - **Remote:** `https://azimo.ai/mcp`, nothing to install; authenticate with the header `Authorization: Bearer `. It cannot read files on your computer: the assistant sends file contents instead, up to 20 MB per file. #### Claude Code ```bash ## Local; you can drop --env if you have run azimo login claude mcp add --env AZIMO_API_KEY="$AZIMO_API_KEY" azimo -- azimo mcp ## Remote claude mcp add --transport http azimo https://azimo.ai/mcp --header "Authorization: Bearer $AZIMO_API_KEY" ``` #### Claude Desktop Use the local server. Add this to `claude_desktop_config.json`; if `azimo` is not found, set `command` to the full path printed by `which azimo`. ```json { "mcpServers": { "azimo": { "command": "azimo", "args": ["mcp"], "env": {"AZIMO_API_KEY": "your-api-key"} } } } ``` #### Cursor Edit `~/.cursor/mcp.json`. The local setup is the same as for Claude Desktop. For the remote server: ```json { "mcpServers": { "azimo": { "url": "https://azimo.ai/mcp", "headers": {"Authorization": "Bearer ${env:AZIMO_API_KEY}"} } } } ``` Then just ask: "Make a 30-second product video for the Pliva Fold Kettle." ### What the assistant can and cannot do **It can** list workflows and the fields each type needs, upload files you point it to, create plans, start production once you agree, follow progress, and hand you the download links. When a video needs input it relays the question, and it can approve a paused video after you say yes. It can also show your balance and cancel videos. **It must show you the plan first.** Production can only start from a plan, and the tools tell the assistant to show you the plan (workflow, length, format) and wait for your explicit yes. Starting production, approving extra spending and cancelling are marked as actions that need confirmation, so most clients ask you before running them. Repeating the start call on the same plan returns the same video. **It cannot** see or quote prices (contact sales@azimo.ai for pricing) or get around your balance and limits. The local server refuses hidden files and folders such as `~/.ssh`, and the remote server cannot read your computer's files at all. ### Stay safe - Give each client its own key, so a leak means revoking just that one. - If the assistant only needs to check progress, give it a `viewer` key: it cannot create plans or start production. - Never paste a key into a chat; keep it in an environment variable or the config file. ### Troubleshooting - Exit code `4` or HTTP `401`: the key is missing, wrong or revoked. Run `azimo login` again. - `403 read-only key`: you are using a `viewer` key; switch to `editor` or `owner`. - `402`: not enough balance, or a spending limit was reached. - The plan is `needs_input`: fix what `requirements` lists; for example `unsupported_duration` means the length is outside the workflow's range. - The MCP client says the server failed to start: run `azimo mcp` in a terminal and read the error on stderr. See [Advanced usage](https://azimo.ai/en/developers/advanced) and the [API reference](https://azimo.ai/en/developers/api) for more. --- ## Advanced usage Source: https://azimo.ai/en/developers/advanced What to know before you go to production: webhooks, safe retries, errors, rate limits, projects and roles, budget approval, revisions and deleting files. Every endpoint and field is listed in the [API reference](https://azimo.ai/en/developers/api); this page is about how to use them. ### Receive results with webhooks Instead of polling, register a URL. Whenever a video's status changes, Azimo POSTs an event to it. You need a key with the `owner` role: ```bash curl -s https://azimo.ai/v1/webhooks \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: webhook-001" \ -d '{"url": "https://example.com/hooks/azimo", "name": "Production updates"}' ``` The response includes a `signing_secret` (it starts with `whsec_`). It is shown only this once, so store it. An event looks like this. Its `type` is `video.` plus the status, such as `video.progress`, `video.needs_input`, `video.complete`, `video.rejected`, `video.failed` or `video.cancelled`: ```json {"id": "d8009f54780243ea", "sequence": 12, "type": "video.complete", "api_version": "v1", "project_id": "c56eb945e4464aec", "created_at": 1790000000, "data": {"id": "b6bf4689035d474f", "status": "complete", "stage": "complete", "progress": 100}} ``` Each delivery carries two headers: `Vidgen-Event-Id` (the event id) and `Vidgen-Signature: t=,v1=`. The signature is the hex `HMAC-SHA256(signing_secret, "." + raw body)`. Always verify the raw bytes you received; do not parse and re-serialize the JSON first: ```python import hashlib, hmac, time def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool: try: parts = dict(item.split("=", 1) for item in header.split(",")) timestamp = int(parts["t"]) except (KeyError, ValueError): return False if abs(time.time() - timestamp) > tolerance: # reject anything older than 5 minutes return False expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` The Python SDK ships the same check as `Vidgen.verify_webhook(secret, header, body)`; in the TypeScript SDK it is `Vidgen.verifyWebhook(secret, header, body)`. When an event arrives: - Once the signature checks out, return a 2xx quickly and do slow work in your own queue. - Failed deliveries are retried for up to about 72 hours, so the same event can arrive more than once: **de-duplicate** by event `id`. - Events can arrive out of order. When in doubt, call `GET /v1/videos/{id}` for the current state. - New event types may be added; ignore any `type` you do not recognise. - Download links expire; fetch the video again when you need fresh ones. You can also set a callback for a single video: add `webhook_url` to `POST /v1/videos`, and use the `webhook_signing_secret` in the response to verify it. If you would rather not run a webhook at all, read events in order with `GET /v1/events?after=`. ### Retry safely with Idempotency-Key Every POST that creates or changes something (uploads, plans, productions, revisions, cancels, answers, webhooks and so on) accepts an `Idempotency-Key` header of 1 to 128 characters. - Same key, same body: you get the first result back and nothing is done twice. - Same key, different body: `409` with the code `idempotency_conflict`. - New key: a new request. Submitting the same plan with a new key makes another video. In practice: generate the key before sending, store it with your own record, and reuse it when you retry after a timeout or a dropped connection. ### Handle errors Every error has the same shape: ```json {"error": {"code": "validation_error", "message": "request validation failed", "details": [{"loc": ["body", "options", "duration_max"], "msg": "Input should be less than or equal to 180", "type": "less_than_equal"}], "request_id": "64ceb19e540b438f8b9a4df3bfe62ba7"}} ``` Branch on `code`, never on the wording of `message`. Every response carries an `X-Request-ID` header; include the `request_id` when you contact us. | HTTP | `code` | What to do | | --- | --- | --- | | 401 | `unauthenticated` | The key is missing, wrong or revoked; use a valid one | | 402 | `insufficient_balance` / `budget_exceeded` | Not enough balance, or a spending limit was reached; top up or contact sales@azimo.ai rather than retrying | | 403 | `forbidden` | Your role or project does not allow it, for example `read-only key` | | 403 | `contact_sales` | Anything about pricing; contact sales@azimo.ai | | 404 | `not_found` | The id does not exist, or is not in the current project | | 409 | `conflict` / `idempotency_conflict` | The state does not allow it (the video has already finished, say), or the key was used for a different request | | 413 | `payload_too_large` / `storage_quota_exceeded` | The file is over 60 MB, or your storage is full; delete unused assets | | 415 | `unsupported_media_type` | That file type is not accepted | | 422 | `validation_error` | A field is wrong; `details` says which | | 429 | `rate_limited` | Too many requests; wait for `Retry-After` and try again | | 500 | `internal_error` | Something broke on our side; contact us with the `request_id` | | 503 | `backend_unconfigured` | This workflow is not available right now | ### Rate limits and 429 - By default each key can make up to 600 API requests per minute. - Each organization can start a limited number of productions per hour, and a key can have its own, lower limit. - Going over returns `429` with a `Retry-After` header giving the seconds to wait. The Python SDK automatically retries 429 and 5xx responses for GETs and for POSTs that carry an `Idempotency-Key`, honouring `Retry-After`. When polling a video, once every 10 seconds or so is enough. ### Projects and key roles Assets, plans, videos and webhooks all belong to a project. Choose one with the `X-Project-ID` header; without it you use your default project. An organization owner can create projects with `POST /v1/projects` and keys with `POST /v1/api-keys`, setting each key's `role` and optional `project_id`. | Role | What it can do | | --- | --- | | `viewer` | Read only: videos, plans, assets, events and balance | | `editor` | Also upload, create plans, start production, cancel, answer and revise | | `owner` (bound to a project) | Also manage that project's webhooks | | `owner` (not bound) | Organization owner: manages all projects and keys | Give each app or client its own key, with the smallest role that does the job. ### Budget approval If a video would go past its spending limit during production, it pauses at `needs_input` and `result.input_required` reads: ```json {"code": "budget_approval", "actor": "customer", "message": "…", "approvable": true} ``` To let it continue, answer: ```bash curl -s https://azimo.ai/v1/videos/b6bf4689035d474f/resume \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: approve-b6bf-001" \ -d '{"approve_budget": true}' ``` In the SDK that is `azimo.approve_budget(video_id)`; on the command line, `azimo resume ID --approve-budget`. Your balance still has to cover the rest, otherwise you get a `402`. Steps that are already done are not redone. Answer any other `needs_input` in words with `{"message": "..."}`, adding `"assets": [""]` when it asks for more material. A video left unanswered for too long (7 days by default) is cancelled automatically. ### Revisions You can revise a video that has ended (`complete`, `rejected`, `failed`, `cancelled` or `needs_input`): ```bash curl -s https://azimo.ai/v1/videos/b6bf4689035d474f/revisions \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: revise-b6bf-001" \ -d '{"prompt": "The full new brief: same product, but end on the two-cup capacity.", "options": {"subtitles": true}}' ``` - You get a new video whose `parent_id` points to the original; the original stays as it is. - `prompt` replaces the original brief entirely, so write it in full. `options` changes only the fields you send. Leave out `assets` to keep the original material; if you have deleted it, you get a `404`, so upload it again and pass `assets`. - A revision is a complete new production and uses your balance. While the original is still in production you get a `409`. ### Cancel and delete - Cancel: `POST /v1/videos/{id}/cancel`. Queued or waiting videos stop at once; one in production stops before its next step. A cancelled video is charged for what it had already used. - No charge for failed QA: a video that ends `rejected` (it did not pass our quality checks) or `failed` (we could not make it) is not charged. Its whole hold is returned, and your wallet entries show a zero-amount `settle` saying why. - Delete an upload: `DELETE /v1/assets/{id}`. Videos already made from it are not affected, but a later revision of such a video needs the material again. - Delete a video's files: `DELETE /v1/videos/{id}`, only once the video has ended (cancel it first). The film, subtitles and cover are removed and the download links stop working; the video record stays. - Brands, templates and webhooks can be deleted with `DELETE` too. - Finished films are kept for a limited time, so download and store them promptly. --- ## 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 `. 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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/" \ -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/" \ -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//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 | 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 | | | | `data[].data.routing` | object | | nullable | | `data[].data.requirements` | array | | | | `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 | | 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 | | | | `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 | | | **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 | | | | `data.routing` | object | | nullable | | `data.requirements` | array | | | | `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": "", "assets": [""]}' ``` #### 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 | | | | `data.routing` | object | | nullable | | `data.requirements` | array | | | | `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/" \ -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 | | 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 | | | | `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 | | | **Response 200** | Name | Type | Required | Description | |---|---|---|---| | `plan_id` | string | yes | | | `workflow` | string | | nullable | | `requirements` | array | | | ```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": "", "assets": [""]}' ``` ### 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 | 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 | | 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 | | 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 | | | | `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 | | | **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 | | 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": "", "assets": [""]}' ``` #### 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 | | up to 30 items | | `assets` | array | | 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 | | 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//resume" \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"message": "", "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 | | 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 | | | | `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 | | 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//revisions" \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"prompt": "", "assets": [""]}' ``` #### 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 | | 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/" \ -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/" \ -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//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//files/?expires=&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 | 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 | yes | up to 50 items | | `items[].prompt` | string | | length ≤ 4000 | | `items[].assets` | array | | 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 | | | | `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 | | | **Response 202** | Name | Type | Required | Description | |---|---|---|---| | `id` | string | yes | | | `name` | string | yes | | | `jobs` | array | 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 | | 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 | 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 | | 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 | 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 | 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 | | 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 | 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 | | 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 | yes | | ```bash curl "https://azimo.ai/v1/batches/" \ -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 | 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 | 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": ""}' ``` #### 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//rotate-secret?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/?expected_version=" \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"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/" \ -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 | 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//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 | 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 | | 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": ""}' ``` #### 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/" \ -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 | | 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/?expected_version=" \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"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/" \ -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 | 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 | | | | `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 | | 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": "", "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/" \ -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 | | | | `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 | | 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/?expected_version=" \ -H "Authorization: Bearer $AZIMO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "", "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/" \ -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 | 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": ""}' ``` #### 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/" \ -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 | 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": ""}' ``` #### 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//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/" \ -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 | 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 | 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` | ```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 | yes | | | `jobs` | map | yes | | | `list_usd` | number | yes | Amount billed for the paid calls in the range | | `group_by` | string | yes | | | `groups` | array | 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" ```