Azimo
  1. Home
  2. Developers
  3. Advanced usage

Guide

Advanced usage

Webhooks, batches, idempotent retries, pagination and error handling.

Copies this page as Markdown with a short Azimo context header, ready to paste into an AI assistant.View Markdown

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; 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:

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:

{"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=<timestamp>,v1=<signature>. The signature is the hex HMAC-SHA256(signing_secret, "<timestamp>." + raw body). Always verify the raw bytes you received; do not parse and re-serialize the JSON first:

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=<sequence>.

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:

{"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.

HTTPcodeWhat to do
401unauthenticatedThe key is missing, wrong or revoked; use a valid one
402insufficient_balance / budget_exceededNot enough balance, or a spending limit was reached; top up or contact sales@azimo.ai rather than retrying
403forbiddenYour role or project does not allow it, for example read-only key
403contact_salesAnything about pricing; contact sales@azimo.ai
404not_foundThe id does not exist, or is not in the current project
409conflict / idempotency_conflictThe state does not allow it (the video has already finished, say), or the key was used for a different request
413payload_too_large / storage_quota_exceededThe file is over 60 MB, or your storage is full; delete unused assets
415unsupported_media_typeThat file type is not accepted
422validation_errorA field is wrong; details says which
429rate_limitedToo many requests; wait for Retry-After and try again
500internal_errorSomething broke on our side; contact us with the request_id
503backend_unconfiguredThis 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.

RoleWhat it can do
viewerRead only: videos, plans, assets, events and balance
editorAlso 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:

{"code": "budget_approval", "actor": "customer", "message": "…", "approvable": true}

To let it continue, answer:

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": ["<asset id>"] 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):

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.
Feedback