Skip to content
LocaleFrame

API reference

One REST call renders a campaign for every market you list. Webhooks tell you when each script, render, score and stream is ready. The API is in private beta; keys are issued to design partners.

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.

header
Authorization: Bearer lf_live_••••••••••••••••

Render a campaign

POSThttps://api.localeframe.com/v1/campaigns/render

Creates 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"
  }'
response · 202
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

FieldTypeDescription
product_urlrequiredstringPublic product detail page. LocaleFrame scrapes imagery, specs, price and claims.
localesrequiredstring[]BCP 47 tags, e.g. ja-JP, de-DE, pt-BR, ar-AE. Up to 35 per request on Scale.
aspect_ratiosstring[]Any of 9:16, 16:9, 1:1. Defaults to ["9:16"].
duration_secondsinteger6, 15 or 30. Defaults to 15.
brand_glossary_idstringLocks product names and claims during transcreation.
scoring.min_hook_retentionnumber0–100. Variants scoring below are re-scripted (max 2 loops). Scale+.
webhook_urlstringHTTPS endpoint for lifecycle events. Overrides the workspace default.
metadataobjectUp 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.

  1. campaign.queued

    Request accepted and persisted; job enqueued on SQS.

  2. script.ready

    Claude finished transcreation for a locale. Includes hook, VO, subtitles and rationale.

  3. render.completed

    Runway shots assembled and muxed for one variant; HLS manifest published.

  4. scoring.completed

    Vertex AI retention and saliency scores attached to a variant.

  5. delivery.live

    Every variant of the campaign is warm on the edge and ready to serve.

  6. render.failed

    A variant failed after 3 retries. Not counted against quota; includes error code.

render.completed · payload
{
  "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.

400invalid_requestMalformed JSON or unsupported parameter value.
401unauthorizedMissing or invalid API key.
402quota_exceededMonthly render quota reached for the workspace plan.
409idempotency_conflictSame Idempotency-Key reused with a different body.
422product_unreachableproduct_url could not be fetched or parsed.
429rate_limitedToo many requests. Respect the Retry-After header.
5xxinternal_errorTransient failure. Safe to retry with the same Idempotency-Key.

Questions about the API? Email support@localeframe.com.