Use case · Agencies

Caption every client in their own style — from one integration

White-label client presets. Webhook delivery into your portal. Per-minute billing

Every client wants their own caption look, and every deliverable has a deadline. ZapCap is the white-label caption-rendering API behind your agency workflow: store a style preset per client, render against it on demand, and deliver finished MP4s straight into your client portal over a webhook.

White-label client presets · webhook → portal · server-only keys
The problem

Many clients, many styles, one deadline at a time

Agency captioning is a multi-tenant problem. Each client has a brand look, a turnaround expectation, and a place they want files delivered. Done in an editor, every clip is a manual ticket; the work scales with headcount, not with the API call it should be.

  • Style fragmentation — a different caption look per client, remembered by hand.
  • Manual deliverables — every clip exported and uploaded by an editor.
  • Client tenancy — keeping one client's files and styling out of another's.
  • Portal delivery — finished files have to land in the right client space.
  • Approval loops — transcript review before render, per client.
  • Margin pressure — caption labour eats the margin on volume work.
Reference architecture

Where ZapCap sits in your agency stack

Drop the API behind your white-label portal. Each client maps to a stored style preset; your backend renders against it and routes the finished file back into that client's space.

Your clients
Client accounts
Per-client style preset
Source clips in your CDN
Your backend
Portal / job runner
Holds ZAPCAP_API_KEY (server-only)
/webhooks/zapcap handler
ZapCap
POST /videos
Task · client templateId
Branded renderUrl
White-label tenancy: store the client → templateId mapping in your own database, and map each taskId back to the client so the webhook files the render into the right portal space.
Backend integration

Per-client presets, delivered to your portal

Look up the client's stored templateId, render against it, and let the webhook drop the finished file into that client's portal space. The agency holds one API key behind a white-label workflow; clients never see ZapCap.

WEBHOOK PAYLOAD
{
  "eventId":   "evt_8kQ2...",
  "taskId":    "tsk_2hP4...",
  "event":     "render.completed",
  "renderUrl": "https://cdn.zapcap.../acme-clip.mp4"
}
  • Map taskId → client when you create the task.
  • Verify the x-signature header before fetching renderUrl.
  • Dedupe webhook deliveries on eventId.
1// Render a client deliverable in that client's stored style
2const KEY = process.env.ZAPCAP_KEY;
3const ZAP = (path, body) =>
4 fetch(`https://api.zapcap.ai${path}`, {
5 method: 'POST',
6 headers: { 'x-api-key': KEY, 'Content-Type': 'application/json' },
7 body: JSON.stringify(body),
8 }).then(r => r.json());
9 
10export async function captionForClient(clientId, clipUrl) {
11 // each client maps to a stored templateId from GET /templates
12 const client = await db.clients.get(clientId);
13 
14 const video = await ZAP('/videos/url', { url: clipUrl });
15 const task = await ZAP(`/videos/${video.id}/task`, {
16 templateId: client.templateId, // the client's saved caption preset
17 notification: {
18 type: 'webhook',
19 notificationsFor: ['render'],
20 recipient: 'https://portal.agency.com/hooks/zapcap',
21 },
22 });
23 
24 // map taskId -> client so the webhook files it in the right portal space
25 await db.deliverables.create({ id: task.taskId, clientId, status: 'processing' });
26 return task.taskId;
27}
28 
29// Webhook → drop the finished file into the client's portal
30export async function onZapcap(req) {
31 const body = await req.text();
32 verifyHmac(req.headers.get('x-signature'), body);
33 const { eventId, taskId, renderUrl } = JSON.parse(body);
34 if (await alreadyProcessed(eventId)) return new Response('ok'); // dedupe on eventId
35 const row = await db.deliverables.update(taskId, { status: 'completed', renderUrl });
36 await portal.deliver(row.clientId, renderUrl);
37 return new Response('ok');
38}
State machine

Lifecycle of a client deliverable

Map each ZapCap status onto the delivery state your portal shows the client — and route the finished file to the right tenant on completion.

pending
transcribing
transcriptionCompleted
rendering
completed
In your portal
Show the client a "Processing your deliverable" state, optionally with a transcript-approval step before render.
On webhook
Verify, file the renderUrl into the client's space, and notify the account owner that the deliverable is ready.
On failure
Re-create the task for that client only and surface a typed error to the account team, not the client.
Launch checklist

Before you onboard your first client

A short list to keep a multi-tenant captioning workflow clean. Tenancy, presets, and delivery in one place.

  • Client → templateId mapping Store each client's saved caption preset (a templateId from GET /templates).
  • taskId → client mapping So the webhook files every render into the right tenant's portal space.
  • White-label server-only API key One agency key, never exposed to clients or client-side code.
  • Optional transcript-approval step Let clients review the transcript before render where the brief needs it.
  • Portal delivery hook On the webhook, drop the file into the client space and notify the account owner.
  • Webhook signature verified Check x-signature on every payload; dedupe on eventId.
  • Per-client usage reporting Track render minutes per client for billing and margin.
Build vs buy

The agency captioning stack, honestly

Build it yourself

In-house multi-tenant pipeline

  • 1ASR vendor wiring — accuracy, retries, language coverage.
  • 2Per-client style engine — reproduce each brand's caption look.
  • 3Tenancy & isolation — keep one client's assets out of another's.
  • 4Render workers — ffmpeg / libass, autoscaling for deadlines.
  • 5Job tracking — taskId → client, retries, observability.
  • 6Portal delivery — route files to the right client space.
  • 7Usage reporting — per-client minute counters for billing.
Use ZapCap

Captioning as a primitive

  • 1Stored templateId per client — render against it on demand.
  • 2POST /videos/:id/task — one call per deliverable.
  • 3Webhook → portal — verify, file by client, notify.
When ZapCap isn't the right answer: if clients need full video assembly from briefs or templated scenes, a broader video automation API (Creatomate, Shotstack, Bannerbear) likely fits better. See our honest alternatives comparisons.
Customer · Anonymized

A creative agency wired the ZapCap API into its client portal and now renders each client's captions in their own saved style, delivered automatically over a webhook.

Captioning stopped being an editor ticket per clip and became a stored preset plus an API call. Finished deliverables land in the right client space without a human moving files between folders.

per client
One stored caption preset each
webhook
Deliverables routed to the portal
1 key
Clients never see ZapCap
per-minute
Billing tracked per client

For agencies

Store a templateId per client in your own database — each is a saved caption preset from GET /templates. When you render a deliverable, look up that client's templateId and pass it on the task, so every clip comes back in the right brand look without anyone remembering it by hand.

Caption every client from one integration

Backend-only API, webhook-native, from $0.10/min base usage pricing. Per-client presets in, branded deliverables in your white-label portal out.