1. Read the job id from the receipt
Every queued Generate endpoint returns the same receipt. Dry runs, avatar records and batch envelopes have their own responses. The job id is always at data.job_id; poll data.poll_url. The same id is in the X-Job-Id response header.
{
"success": true,
"data": {
"job_id": "6a10f0c2a1b2c3d4e5f60718",
"video_id": "6a10f0c2a1b2c3d4e5f60719",
"status": "queued",
"poll_url": "/api/v1/jobs/6a10f0c2a1b2c3d4e5f60718",
"price_usd_cents": 750,
"delivery": { "visibility": "private", "link_ttl_seconds": 86400 }
},
"jobId": "6a10f0c2a1b2c3d4e5f60718",
"videoId": "6a10f0c2a1b2c3d4e5f60719",
"meta": { "request_id": "…" }
}data.job_id,data.video_id,data.statusanddata.poll_urlare on every Generate success: stock video (202), YouTube clips, AI video, AI influencer,/videos/magicand/blog-to-video.data.price_usd_centsis what this request charged (when it was charged);data.deliveryappears when you asked for private delivery.- Product-specific fields stay alongside (for example
requested_clip_counton clips,distributionon stock). - Deprecated: the top-level
jobIdandvideoIdare kept as aliases so existing integrations keep working. New code should readdata.job_id.
JOB_ID=$(echo "$RESPONSE" | jq -r '.data.job_id')To check a request and its price before spending, send the same body with "dry_run": true. It is free, needs no Idempotency-Key, creates no job and returns data.dry_run, data.price and the data.normalized_request; invalid input gets the same 400 as a real call. See Dry runs.
2. Poll the job
curl https://api.reelsbuilder.ai/api/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $REELSBUILDER_API_KEY"{
"success": true,
"data": {
"job": {
"id": "6a10…",
"kind": "generation",
"operation": "clip",
"status": "completed",
"attempts": 1,
"progress": [{ "at": "…", "stage": "rendering", "message": "…", "percent": 80 }],
"artifacts": {
"videoId": "6a10…",
"videoUrl": "https://cdn.reelsbuilder.ai/…/clip-1.mp4",
"videoUrls": ["https://cdn.reelsbuilder.ai/…/clip-1.mp4"],
"thumbnailUrl": "https://cdn.reelsbuilder.ai/….jpg",
"title": "3 signs your pipeline review is broken",
"description": "… #revops #b2bsales",
"script": "Your sales team spends hours …",
"durationSec": 30.1,
"clips": [
{ "videoUrl": "https://cdn.reelsbuilder.ai/…/clip-1.mp4", "title": "…", "transcript": "…", "viralScore": 8.7, "durationSec": 41.2 }
]
},
"error": null,
"createdAt": "…",
"updatedAt": "…"
}
}
}Poll every 10–15 seconds. Job reads are free but count toward the 60-reads-per-minute limit per key, so with many jobs in flight poll them in a loop with a shared delay rather than in parallel bursts.
3. Statuses
| Status | Meaning | Final? | What to do |
|---|---|---|---|
queued | Accepted, waiting for a worker | No | Keep polling |
leased / running | Being generated | No | Keep polling |
uploading | Rendering done, storing the files | No | Keep polling |
completed | Finished | Yes | Read artifacts |
failed | Could not be completed; charges refunded | Yes | Read error; retry only if error.retryable |
dead_lettered | Failed after all automatic retries; charges refunded | Yes | Treat like failed |
cancelled | You cancelled it | Yes | Nothing |
A job may run more than one attempt internally (attempts); you only need to watch status.
4. Download the results
artifacts.videoUrl— the main MP4.artifacts.videoUrls— every delivered file. Clip jobs return one entry per clip; count what you received, not what you requested.artifacts.thumbnailUrl— a poster image.artifacts.titleandartifacts.description— ready-to-post copy we generate for the video (the description includes hashtags).artifacts.script— the narration (stock and AI video) or the clip transcript.artifacts.durationSec— measured length of the main video.artifacts.clips[]— clip jobs only: each clip'svideoUrl,title,transcript,viralScoreanddurationSec, highest score first.
Files are served from our CDN. Copy them into your own storage when the job completes rather than hot-linking: generated files have a retention window. For files that are never on a public URL, use private delivery.
5. How long jobs take
| Product | Typical time |
|---|---|
| Stock video (30s) | About 3–6 minutes |
| YouTube clips | About 3–7 minutes for a short source; longer sources take longer |
| AI video (30s) | About 6–8 minutes; 1 minute takes roughly twice as long |
| AI influencer, talking head | About 4–7 minutes |
AI influencer, presenter_broll (30–45s) | About 15–25 minutes for one video; 35–45 minutes each when two or more run at the same time |
The presenter figures were measured on production in October 2026 (42 videos: half finished within 20 minutes, nine in ten within 39, the slowest took 46). A presenter video is planned, searched for footage and edited shot by shot, so it is the slowest product. Send a webhook_url instead of holding a connection open, and submit them one or two at a time if you need each one sooner.
When you submit several stock videos at once they wait in queued while capacity scales up, so a batch of five can take 10–20 minutes end to end. Keep polling; queued is normal. If a job is still not final after 45 minutes (75 minutes for a presenter video), contact support with the job id.
6. Failures, refunds and retries
A failed job looks like this:
"error": {
"code": "AI_OUTPUT_LOW_QUALITY",
"category": "provider",
"message": "The generated video didn't pass our quality check.",
"retryable": true
}- Plan tokens and API credits for a failed or dead-lettered job are refunded automatically.
- If
retryableis true, submit a new request with a newIdempotency-Key— the old key replays the old result. - If
retryableis false, fix the input first (see Errors).
Submitting many jobs at once (batch)
POST /api/v1/batch takes up to 25 stock, YouTube clip, text-to-video, image-to-video or AI influencer jobs in one call and answers with one result per job. Each job runs through its normal endpoint, so prices, validation, limits and refunds are exactly the same as sending them one by one. Add "dry_run": true at the top level to validate and price every job without queuing anything (no key needed); the answer has data.valid and the total in data.price.
curl -X POST https://api.reelsbuilder.ai/api/v1/batch \
-H "Authorization: Bearer $REELSBUILDER_API_KEY" \
-H "Idempotency-Key: acme-campaign42-batch1" \
-H "Content-Type: application/json" \
-d '{
"jobs": [
{ "endpoint": "generate-and-post", "body": { "prompt": "Why zero-trust...", "clip_type": "tech", "brand": { "name": "Acme Cloud" } } },
{ "endpoint": "clipper/youtube", "body": { "channel_url": "https://www.youtube.com/@acmecloud", "clip_count": 3 } },
{ "endpoint": "videos/ai-influencer", "body": { "image_url": "https://cdn.acme.com/host.jpg", "script": "..." } }
]
}'
# -> 200 { "data": { "accepted": 3, "rejected": 0, "results": [
# { "index": 0, "endpoint": "generate-and-post", "ok": true, "http_status": 202, "job_id": "...", "response": { ... } }, ... ] } }endpointis one ofgenerate-and-post,clipper/youtube,videos/text-to-video,videos/image-to-video,videos/ai-influencer;bodyis that endpoint's normal request body.- Each
results[].job_idis read from that job'sdata.job_id. - Each job uses the idempotency key
<your batch key>:<index>, or its ownidempotency_key. Re-sending the same batch is safe: accepted jobs return their original result instead of rendering again. - A rejected job (for example
402,400or429) does not affect the others. Fix it and send it again with a new key. - Stock renders still run up to 5 at a time per account; extra jobs wait in
queued, they are not rejected.
7. Cancel a job
curl -X POST https://api.reelsbuilder.ai/api/v1/jobs/$JOB_ID/cancel \
-H "Authorization: Bearer $REELSBUILDER_API_KEY"Works while the job is not final; returns 409 JOBS_CANCEL_NOT_ALLOWED once it is finishing.
8. Webhooks
Every Generate endpoint accepts an optional webhook_url that receives one signed callback when the job ends, with the file links, title and description. See Webhooks.
9. Sandbox keys
With an rb_test_ key, every Generate call returns a placeholder job (sandbox_job_…) and polling it returns the same data.job shape as a live job, already completed with example artifacts. Code written against the sandbox works unchanged with a live key.
10. Private delivery
By default finished files sit on our public CDN at an unguessable URL. Add "delivery": { "visibility": "private" } to the request to keep them private instead:
- Files are kept in a private bucket with no public address. Nothing is left on the public CDN.
- Each poll of
GET /api/v1/jobs/{job_id}returns signed links in the usualartifacts.videoUrl,videoUrls,thumbnailUrlandclips[].videoUrlfields, plusartifacts.delivery.expires_at. - Links expire after 24 hours. Poll the job again for a fresh link; every read signs new ones. Do not store a link as the file's permanent address.
- Links are only returned to the API key's account that owns the job. Anyone you share a link with can download the file until it expires.
- The completion webhook carries no link (
video_urlisnull); poll the job to get one. - The account library (
GET /api/v1/b2b/library/videos) returns the same signed links inmedia_accessfor private videos.
| Supported on | Not supported (returns 422 PRIVATE_DELIVERY_UNAVAILABLE) |
|---|---|
POST /api/v1/generate-and-post, /jobs, /clipper/youtube, /videos/text-to-video, /videos/image-to-video, /videos/ai-influencer | Other generation routes, such as /videos/magic, /videos/motivational and /blog-to-video |
curl -X POST https://api.reelsbuilder.ai/api/v1/clipper/youtube -H "Authorization: Bearer $REELSBUILDER_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{ "youtube_url": "https://www.youtube.com/watch?v=VIDEO_ID", "delivery": { "visibility": "private" } }'
# The receipt confirms it:
# "delivery": { "visibility": "private", "link_ttl_seconds": 86400, ... }
# A completed poll returns:
# "artifacts": {
# "videoUrl": "https://<signed-storage-host>/…/video-0.mp4?X-Amz-Expires=86400&X-Amz-Signature=…",
# "delivery": { "visibility": "private", "expires_at": "2026-10-08T12:00:00.000Z" },
# …
# }If the files cannot be moved to private storage the job fails with a non-retryable error and the charge is refunded; it is never delivered on a public URL instead.