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.
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 |
The full flow
Section titled “The full flow”-
Quote it, optionally
Section titled “Quote it, optionally”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.
-
Queue the job
Section titled “Queue the job”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 adata:URI.If this returns
409, see Consent below. -
Poll until it’s done
Section titled “Poll until it’s done”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
200status, and which one you get depends on whether the job has finished. See Branch on Content-Type. -
Release the stored media
Section titled “Release the stored media”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.
Branch on Content-Type
Section titled “Branch on Content-Type”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)import { writeFile } from 'node:fs/promises'
const body = JSON.stringify({ model: 'VIDEO_MODEL_ID', queue_id: queueId })const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }
for (;;) { const res = await fetch('https://api.bayle.ai/video/retrieve', { method: 'POST', headers, body }) if (!res.ok) throw new Error(`retrieve failed: ${res.status}`)
if (res.headers.get('content-type')?.startsWith('video/')) { await writeFile('out.mp4', Buffer.from(await res.arrayBuffer())) break }
const status = await res.json() if (status.download_url) { const file = await fetch(status.download_url) await writeFile('out.mp4', Buffer.from(await file.arrayBuffer())) break }
console.log(status.status, status.execution_duration) await new Promise((r) => setTimeout(r, 10_000))}Bayle sends no retry-delay header and enforces no polling cadence. The 10 second figure is guidance for you, not a signal to parse.
About download_url
Section titled “About download_url”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.
Consent
Section titled “Consent”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.