Skip to content
Try chatGet API key

Video

Video generation is asynchronous. Unlike the other endpoints, one call does not get you a result: you queue a job, poll it, and release the stored media when you’re done.

Copies this page's setup instructions, to paste into your coding agent.
Show the prompt
Help me set this up to use Bayle AI, an OpenAI-compatible API. Full guide: https://docs.bayle.ai/guides/video/

Goal: generate a video through Bayle from this project: quote, queue, poll, save, release.

1. Pick a video model: curl -s "https://api.bayle.ai/models?type=video" | jq -r '.data[].id'
   Show me the list and let me choose VIDEO_MODEL_ID.
2. Quote the job first. This call needs no key:
   curl https://api.bayle.ai/video/quote -H "Content-Type: application/json" -d '{"model": "VIDEO_MODEL_ID", "duration": "5s", "resolution": "720p", "audio": false}'
   Show me the price and wait for my go-ahead before spending anything.
3. Queue it: POST https://api.bayle.ai/video/queue with a bearer key and {"model": "VIDEO_MODEL_ID", "prompt": "A paper boat drifting down a rain gutter.", "duration": "5s"}. Keep the returned `queue_id`. For image-to-video add `image_url` (a public https URL or a data: URI).
4. A 409 with code needs_consent means the model needs a consent attestation for faces. Nothing was charged. Show me `consent.policy_text` and only resend the identical body with the `consents` object (all three booleans true) if I confirm the person in the media has consented.
5. Poll POST /video/retrieve with {"model": "VIDEO_MODEL_ID", "queue_id": "QUEUE_ID"} about every 10 seconds. A 200 is either JSON (still running: `status`, `execution_duration`) or `video/mp4` (done). Branch on the response Content-Type, never on a body field. Some models instead return a pre-signed `download_url` in the JSON: fetch it directly with no credential. Handle both, and save the video as out.mp4.
6. Release the stored media: POST /video/complete with the same model and queue_id. A 400 reporting the job as not found is expected after a model that returned the bytes inline; do not treat it as a failure.
7. Test: run the whole flow once with the shortest duration and show me out.mp4's size.

Rules:
1. The base URL is https://api.bayle.ai exactly as written. Append no path to it.
2. Read the key from the BAYLE_API_KEY environment variable. Never write the key itself into a file you create, a command, a commit or the chat. Where the tool keeps the key in its own settings screen, tell me where to paste it. If BAYLE_API_KEY is not set, stop and ask me to create a key at https://chat.bayle.ai/developers and export it.
3. Take model ids only from GET https://api.bayle.ai/models (no key needed). Never guess one.
4. Finish with one real test request, show me the result and list the files you changed.

Four endpoints, in order:

Endpoint Purpose Key required
POST /video/quote Price a job before committing No
POST /video/queue Start the job, get a queue_id Yes
POST /video/retrieve Poll progress, then get the video Yes
POST /video/complete Release the stored media Yes
  1. No credential needed, so you can price a job before you hold one.

    Terminal window
    curl https://api.bayle.ai/video/quote \
    -H "Content-Type: application/json" \
    -d '{"model": "VIDEO_MODEL_ID", "duration": "5s", "resolution": "720p", "audio": false}'

    The decimal price is returned exactly as computed, never decoded and re-encoded, so it’s safe to compare or store verbatim.

  2. Terminal window
    curl https://api.bayle.ai/video/queue \
    -H "Authorization: Bearer $BAYLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "model": "VIDEO_MODEL_ID",
    "prompt": "A paper boat drifting down a rain gutter.",
    "duration": "5s"
    }'

    You get back a queue_id. Hold onto it: the next two calls both need it.

    For image-to-video, add image_url, either a public https URL or a data: URI.

    If this returns 409, see Consent below.

  3. Poll about every 10 seconds. Video generation takes meaningfully longer than the synchronous endpoints, so don’t expect the first call to be ready.

    Terminal window
    curl https://api.bayle.ai/video/retrieve \
    -H "Authorization: Bearer $BAYLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model": "VIDEO_MODEL_ID", "queue_id": "QUEUE_ID"}'

    This endpoint returns one of two entirely different things under the same 200 status, and which one you get depends on whether the job has finished. See Branch on Content-Type.

  4. Terminal window
    curl https://api.bayle.ai/video/complete \
    -H "Authorization: Bearer $BAYLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model": "VIDEO_MODEL_ID", "queue_id": "QUEUE_ID"}'

    Bayle keeps no record of a queue_id, so calling this twice is answered on its merits rather than short-circuited.

POST /video/retrieve returns either a JSON status document or the raw video, both with a 200. Branch on the response’s Content-Type, not on a body field. Parsing the response as JSON unconditionally will fail on the video.

Content-Type Meaning
application/json Still running. Carries status, average_execution_time, execution_duration.
video/mp4 Finished. The body is the video.
import time, httpx, pathlib
body = {"model": "VIDEO_MODEL_ID", "queue_id": queue_id}
headers = {"Authorization": f"Bearer {api_key}"}
while True:
res = httpx.post("https://api.bayle.ai/video/retrieve", json=body, headers=headers, timeout=120)
res.raise_for_status()
if res.headers["content-type"].startswith("video/"):
pathlib.Path("out.mp4").write_bytes(res.content)
break
status = res.json()
# Some models put a download_url here instead of returning the bytes above.
if url := status.get("download_url"):
pathlib.Path("out.mp4").write_bytes(httpx.get(url, timeout=300).content)
break
print(status.get("status"), status.get("execution_duration"))
time.sleep(10)

Bayle sends no retry-delay header and enforces no polling cadence. The 10 second figure is guidance for you, not a signal to parse.

Some models deliver the finished video through a download_url in the status document rather than as bytes from retrieve. It’s a pre-signed URL your own client fetches directly: it needs no credential, and it does not pass back through Bayle. Handle both shapes, as the examples above do.

Some models require a consent attestation before they will generate media containing a face. The first queue call comes back 409, and nothing is charged:

{
"error": { "code": "needs_consent", "message": "..." },
"consent_flow": "...",
"face_media_roles": ["..."],
"consent": { "consent_version": "...", "policy_text": "..." },
"docs_url": "https://docs.bayle.ai"
}

consent.policy_text is the text to show the person whose likeness appears in the media, and face_media_roles tells you which parts of your request need that confirmation.

To proceed, resend the identical request body to the same endpoint with a consents object added. All three booleans must be literally true:

{
"model": "VIDEO_MODEL_ID",
"prompt": "A paper boat drifting down a rain gutter.",
"duration": "5s",
"consents": {
"seedance": {
"confirmed_terms_and_privacy": true,
"confirmed_legal_right": true,
"confirmed_screening_acknowledged": true
}
}
}

There is no consent token to carry, no version for you to send back, and no separate endpoint for the retry. It is the same POST /video/queue call with one more field. The key inside consents names the model family, which the 409 body tells you.

Only attest on behalf of someone who has actually consented. That is what the confirmation is for.