API reference
Overview
The API is organized around REST, accepts JSON request bodies and returns JSON. Rendering is asynchronous: requests return 202 Accepted immediately and progress is reported via webhooks or by polling the campaign resource.
- Base URL
https://api.localeframe.com/v1- Format
- JSON
- Auth
- Bearer token
Authentication
Authenticate with a workspace API key in the Authorization header. Keys are scoped to a workspace, can be restricted to specific endpoints, and are shown once at creation. During the beta, API access is limited to design partners — apply here.
Authorization: Bearer lf_live_••••••••••••••••Render a campaign
POSThttps://api.localeframe.com/v1/campaigns/renderCreates a campaign and queues one variant per locale × aspect ratio. Supply an Idempotency-Key header to make retries safe.
curl -X POST https://api.localeframe.com/v1/campaigns/render \
-H "Authorization: Bearer $LOCALEFRAME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e0a-launch-q4" \
-d '{
"product_url": "https://shop.example.com/products/botanica-no7-night-serum",
"locales": ["ja-JP", "de-DE", "pt-BR"],
"aspect_ratios": ["9:16", "16:9"],
"duration_seconds": 15,
"brand_glossary_id": "gls_8KD2",
"scoring": { "min_hook_retention": 85 },
"webhook_url": "https://hooks.yourbrand.com/localeframe"
}'HTTP/1.1 202 Accepted
{
"id": "cmp_01J9Z3K8W4QX",
"object": "campaign",
"status": "queued",
"variants_requested": 6,
"estimated_ready_seconds": 85,
"links": {
"self": "https://api.localeframe.com/v1/campaigns/cmp_01J9Z3K8W4QX"
},
"created_at": "2026-10-24T14:02:11Z"
}Parameters
| Field | Type | Description |
|---|---|---|
product_urlrequired | string | Public product detail page. LocaleFrame scrapes imagery, specs, price and claims. |
localesrequired | string[] | BCP 47 tags, e.g. ja-JP, de-DE, pt-BR, ar-AE. Up to 35 per request on Scale. |
aspect_ratios | string[] | Any of 9:16, 16:9, 1:1. Defaults to ["9:16"]. |
duration_seconds | integer | 6, 15 or 30. Defaults to 15. |
brand_glossary_id | string | Locks product names and claims during transcreation. |
scoring.min_hook_retention | number | 0–100. Variants scoring below are re-scripted (max 2 loops). Scale+. |
webhook_url | string | HTTPS endpoint for lifecycle events. Overrides the workspace default. |
metadata | object | Up to 20 string key/value pairs echoed back on every event. |
Webhooks
Events are delivered as signed POST requests. Respond with any 2xx within 10 seconds; failed deliveries are retried with exponential back-off for up to 24 hours. Events for a single variant are delivered in order.
campaign.queuedRequest accepted and persisted; job enqueued on SQS.
script.readyClaude finished transcreation for a locale. Includes hook, VO, subtitles and rationale.
render.completedRunway shots assembled and muxed for one variant; HLS manifest published.
scoring.completedVertex AI retention and saliency scores attached to a variant.
delivery.liveEvery variant of the campaign is warm on the edge and ready to serve.
render.failedA variant failed after 3 retries. Not counted against quota; includes error code.
{
"id": "evt_01J9Z3QF7R",
"type": "render.completed",
"created_at": "2026-10-24T14:03:27Z",
"data": {
"campaign_id": "cmp_01J9Z3K8W4QX",
"variant": {
"locale": "ja-JP",
"aspect_ratio": "9:16",
"duration_seconds": 15,
"render": { "resolution": "1080x1920", "fps": 60, "codec": "h264" },
"scores": { "hook_retention": 92.4, "thumbstop_pred": 31.8 },
"hls_url": "https://edge.localeframe.com/v1/hls/cmp_01J9Z3K8W4QX/ja-JP/master.m3u8"
}
}
}Signature verification
Every request carries a LocaleFrame-Signature header of the form t=timestamp,v1=hex. Compute an HMAC-SHA256 of {t}.{raw_body} with your endpoint secret, compare in constant time, and reject timestamps older than five minutes.
import crypto from "node:crypto";
export function verifyLocaleFrame(rawBody: string, header: string, secret: string) {
// header: "t=1761314607,v1=5d1c…"
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Errors & rate limits
Default limits are 60 requests/minute on Scale and custom on Enterprise. Responses include X-RateLimit-Remaining and, when limited, Retry-After.
| 400 | invalid_request | Malformed JSON or unsupported parameter value. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | quota_exceeded | Monthly render quota reached for the workspace plan. |
| 409 | idempotency_conflict | Same Idempotency-Key reused with a different body. |
| 422 | product_unreachable | product_url could not be fetched or parsed. |
| 429 | rate_limited | Too many requests. Respect the Retry-After header. |
| 5xx | internal_error | Transient failure. Safe to retry with the same Idempotency-Key. |
Questions about the API? Email support@localeframe.com.