Download a template
| Tool | File | How to import |
|---|---|---|
| n8n | n8n-stock-video-to-post.json | Workflows → Import from file. Create a Header Auth credential and select it on the three HTTP Request nodes. |
| Make | make-stock-video-scenario.json | Create a scenario → … menu → Import Blueprint. Set the key as the custom variable REELSBUILDER_API_KEY. Beta: if the import fails in your Make account, rebuild it from the n8n steps or email admin@reelsbuilder.ai. |
| Zapier | zapier-webhooks.md | Step-by-step with Webhooks by Zapier (Zapier has no importable file format). |
The files contain no keys. Every tool keeps the key in its own credential store and sends it as Authorization: Bearer rb_…. Create a key in Settings → API. Building with an AI coding agent instead? See the agent quickstart.
What every template does
- Quote (free).
POST /api/v1/generate-and-postwith the real body plus"dry_run": true. No job, no charge, noIdempotency-Key. Readdata.validanddata.price.usd_cents. - Check the price. Continue only if the quote is at or below your budget (stock video is a flat $0.60, any length from 15 to 120 seconds).
- Create. The same body without
dry_run, with anIdempotency-Keyheader (8–256 characters, one per creative). The 202 receipt always hasdata.job_idanddata.poll_url. - Wait. By webhook (preferred) or by polling
GET /api/v1/jobs/{job_id}until the status iscompletedorfailed(also stop ondead_letteredandcancelled). - Use the result. The finished files are in
data.job.artifacts:videoUrl,videoUrls,thumbnailUrl,titleanddescription(hashtags included). Copy the MP4 to your own storage; CDN files have a retention window.
Prices for other modes: YouTube clips $0.40 per delivered clip, AI influencer from $1.00 per 30 seconds, AI video from $4.30 per 30 seconds. The dry run always returns the exact charge. Live list: /capabilities.
Generate and wait: webhook first, polling as fallback
Preferred: webhook
Add webhook_url (public https) to the create call. When the job ends, ReelsBuilder sends one signed POST with event, job_id, status, video_url, video_urls, title, description, error and agent_run_id. Your tool needs no long-running loop:
- n8n: a second workflow that starts with a Webhook trigger (POST, raw body on).
- Make: a second scenario that starts with Webhooks → Custom webhook.
- Zapier: a second Zap that starts with Webhooks by Zapier → Catch Raw Hook.
Up to 3 attempts (immediately, after 30 seconds, after a further 120 seconds). The same job can arrive twice, so de-duplicate on job_id. Send agent_run_id (for example your sheet row or CRM id) to match the callback to your record. Details: Webhooks.
Fallback: polling with backoff
If you cannot receive a webhook, poll the job. A 30-second stock video usually takes 3–6 minutes. Job reads are free but limited to 60 per minute per key, so back off:
- First poll after 15 seconds.
- Grow the delay each round (×1.5 or +10 seconds), capped at 60 seconds.
- Stop after 30 minutes and flag the job for a human; do not resubmit it.
- On
429 RATE_LIMIT_EXCEEDED, wait theRetry-Afterseconds.
// Same loop as the n8n and Make templates
const TERMINAL = ["completed", "failed", "dead_lettered", "cancelled"];
async function waitForJob(jobId: string) {
const started = Date.now();
let delay = 15;
for (;;) {
await new Promise((r) => setTimeout(r, delay * 1000));
const res = await fetch(`https://api.reelsbuilder.ai/api/v1/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${process.env.REELSBUILDER_API_KEY}` },
});
const job = (await res.json()).data.job;
if (TERMINAL.includes(job.status)) return job; // job.artifacts or job.error
if (Date.now() - started > 30 * 60_000) throw new Error(`Job ${jobId} still ${job.status}`);
delay = Math.min(60, Math.round(delay * 1.5));
}
}Verify webhook signatures
Get your account's secret once with GET /api/v1/webhook-secret (free) and store it with your other secrets. Each callback has the header X-ReelsBuilder-Signature: t=<unix>,v1=<hex>, where v1 is HMAC-SHA256 with your secret over <t>.<raw body>. Use the raw body (before JSON parsing), compare in constant time and reject timestamps older than 300 seconds.
Node.js
import crypto from "node:crypto";
export function verifyReelsBuilder(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}Python
import hashlib, hmac, time
def verify_reelsbuilder(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > 300 or "v1" not in parts:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts["v1"], expected)In n8n, verify in a Code node after the Webhook trigger (enable Raw Body); in Zapier, use Code by Zapier after Catch Raw Hook (snippet in the Zapier recipe). Whatever the tool, confirm the result with GET poll_url before publishing.
Per-client branding with the brand object
Agencies running many clients from one account send each client's brand on every request. When brand is sent it replaces the account brand (nothing is merged). Add "include_branding": false so only the client's logo appears.
{
"prompt": "How Acme cuts onboarding time in half for IT teams",
"content_purpose": "sales",
"clip_type": "tech",
"cta": { "text": "Book a demo at acme.com" },
"include_branding": false,
"brand": {
"name": "Acme",
"website_url": "https://acme.com",
"logo_url": "https://cdn.acme.com/logo.png",
"primary_color": "#0A66C2",
"accent_color": "#00C2FF",
"tone": "professional",
"industry": "SaaS",
"keywords": ["onboarding", "IT"]
}
}name(≤80),website_url(https),logo_url(public https image, bottom-right watermark),primary_color/accent_color(#RRGGBB, caption highlight),tone(professional, casual, energetic, bold, calm, analytical),industry(≤80, also picks the footage niche whenclip_typeis omitted),keywords(≤10).- Unknown fields or bad values return a free
400 INPUT_INVALID; the dry run catches them. - Keep one row per client (sheet, Airtable, CRM) with these fields and map them into the request. Use the client and creative ids in the
Idempotency-Key, for exampleacme-2026-10-07-zero-trust. - More than one video per run?
POST /api/v1/batchtakes up to 25 jobs, each with its own body and brand.
Errors in workflow tools
Every error uses one envelope: { "success": false, "error": { "code", "message", "retryable" } }. Branch on error.retryable:
402 INSUFFICIENT_API_CREDITS: stop the workflow and alert a human; never retry automatically.429 RENDER_CONCURRENCY_LIMIT_EXCEEDED: up to 5 stock renders run at once per account; queue the rest.- Network error on the create call: retry with the same
Idempotency-Key; a finished request replays without a second charge. - Job
failed: the charge is refunded. Resubmit with a new key only iferror.retryableis true.
Full list: Errors. Questions: admin@reelsbuilder.ai with the meta.request_id or job id.