API Reference
Last updated March 2026
On this page
Overview
The Rewind Reels API powers the video generation pipeline, public player, billing, and administration features. The base URL is https://api.rewind.purposeforce.org. All endpoints accept and return JSON unless otherwise noted. Errors follow a consistent shape:
{ "error": "Human-readable error message" }Authentication
Three authentication methods are used depending on the endpoint category:
| Method | Header | Used By |
|---|---|---|
| API Key | x-rewind-api-key: rw_00D5f000000XXXX_hmac... | Pipeline endpoints (generate) |
| Basic Auth (Admin) | x-admin-key: <REWIND_ADMIN_KEY> | Admin endpoints (licenses, override) |
| None (Public) | (no auth header) | Health, registration, player, billing checkout/portal, OG image |
Rewind_License__c.Api_Key__c field. Never expose API keys in client-side code. All API calls originate from Apex server-side code.Rate Limits
Rate limiting prevents abuse and ensures fair usage across all orgs.
| Endpoint Category | Limit | Key |
|---|---|---|
| Pipeline (authenticated) | 30 requests / minute | API key prefix (first 16 chars) |
| Player (public) | 30 requests / minute | Client IP address |
| License check | 30 requests / minute | Client IP address |
| Billing portal | 30 requests / minute | Client IP address |
| Admin override | 30 requests / minute | Client IP address |
When rate-limited, endpoints return 429 Too Many Requests.
Architecture
The Rewind Reels pipeline is orchestrated from the Salesforce org. The LWC Dashboard triggers Apex, which calls the backend API in sequence. Here is the end-to-end flow:
Salesforce Org
|-- LWC Dashboard --> Apex Controller
|-- RewindGenerateQueueable (sends sessionToken + shareToken)
|
`-- HTTP POST --> /api/pipeline/generate
|-- Claude AI (Anthropic) generates narration + theme
|-- Caches narration + theme in Vercel Blob (keyed by shareToken)
|-- Callbacks to SF using sessionToken (stores on Rewind_Video__c)
`-- Returns: narration JSON, theme, status
Public Player (rewind.purposeforce.org/v/:token)
|-- GET /api/player/:token
| |-- Checks Vercel Blob cache first (fast, no SF query)
| |-- Falls back to Salesforce SOQL if not cached
| `-- Returns JSON to client
|
`-- Client-side animated HTML player
|-- Renders scenes with spring animations, cross-fades
|-- Plays background music via Web Audio API
`-- Tracks views + completion via POST callsThe public player (rewind.purposeforce.org/v/:token) fetches narration JSON and theme data via the player API. Data is served from Vercel Blob cache when available, falling back to a Salesforce SOQL query. No video files are generated — the player renders scenes with spring-driven animations, cross-fades, and optional background music. View and completion analytics are tracked with fire-and-forget POST calls.
Health Check
/api/healthSimple liveness probe. No authentication required. Response is cached for 60 seconds.
Response
{
"status": "ok",
"version": "12"
}| Status | Description |
|---|---|
| 200 | Service is healthy |
Registration
/api/registerRegisters a new Salesforce org and provisions an API key. Always assigns the free tier regardless of any tier value sent in the request body. Tier upgrades must go through the billing checkout flow.
Request Body
{
"orgId": "00D5f000000XXXX", // Required. 15 or 18 char Salesforce org ID
"orgType": "production" // Optional. Informational only
}Response
{
"apiKey": "rw_00D5f000000XXXX_a1b2c3d4e5f6...",
"tier": "free",
"rendersLimit": 3,
"verification": "sha256-hash-string",
"licenseToken": "base64url-payload.base64url-signature"
}| Status | Description |
|---|---|
| 200 | Registration successful |
| 400 | Missing or invalid orgId |
| 500 | Registration failed |
| 503 | Server configuration error (missing secrets) |
License Check
/api/license/checkCalled by the Salesforce org to check the current tier (e.g., after a billing upgrade). Looks up the Stripe customer by API key and returns the current tier and a fresh license token.
Request Body
{
"apiKey": "rw_00D5f000000XXXX_a1b2c3...", // Required
"orgId": "00D5f000000XXXX" // Required
}Response
{
"tier": "professional",
"rendersLimit": 75,
"licenseToken": "base64url-payload.base64url-signature"
}| Status | Description |
|---|---|
| 200 | License check successful. Returns free tier if no Stripe customer found. |
| 400 | Missing apiKey or orgId, or invalid API key format |
| 429 | Rate limit exceeded |
| 500 | License check failed |
Pipeline: Narrate
/api/pipeline/narrateGenerates a narration script from Salesforce data using Claude AI (Anthropic). This is step 1 of the pipeline. Requires API key authentication. Max execution time: 60 seconds.
Request Body
{
"data": {
"records": [ ... ], // Required. Array of Salesforce records
"recordCount": 42,
"objectLabel": "Opportunity",
"aggregates": { ... } // Optional. Pre-computed aggregates
},
"presetType": "sales_cloud", // Preset template identifier
"tone": "professional", // professional | motivational | analytical
"audience": "Sales Team",
"customPrompt": "Focus on deals closing this week",
"periodLabel": "Week of March 3",
"videoLength": "standard" // quick | standard | deep_dive
}Response
{
"narration": {
"title": "Sales Pipeline Recap — Week of March 3",
"scenes": [
{
"type": "intro",
"headline": "Your Weekly Pipeline",
"body": "Let's look at how the team performed..."
},
{
"type": "metric",
"headline": "$2.4M in Pipeline",
"body": "Pipeline grew 12% week over week...",
"metric": { "value": "$2.4M", "label": "Total Pipeline" }
}
]
},
"narrationTitle": "Sales Pipeline Recap — Week of March 3"
}| Status | Description |
|---|---|
| 200 | Narration generated successfully |
| 400 | No data provided (empty records array) |
| 401 | Invalid or missing API key |
| 429 | Rate limit exceeded |
| 500 | Narration generation failed |
Pipeline: Generate
/api/pipeline/generateCombined generation route — narrates data with Claude AI, builds the player theme, caches the result in Vercel Blob, and callbacks to Salesforce with narration JSON + theme using the provided session token. The resulting narration JSON is rendered client-side as an animated HTML player. Max execution time: 120 seconds.
Request Body
Same as the Narrate route, plus:
{
"videoId": "a0x5f000001XXXX", // Required. Rewind_Video__c record ID
"instanceUrl": "https://your-instance.my.salesforce.com", // Salesforce org instance URL
"sessionToken": "00D...!AQ...", // Session token from Apex (for callback auth)
"shareToken": "a1b2c3d4e5f6...", // 32-char hex token (for Vercel Blob caching)
"data": { ... }, // Required. Same format as narrate
"brand": { // Optional. Branding overrides
"primaryColor": "#7C3AED",
"secondaryColor": "#F43F5E",
"logoUrl": "https://...",
"backgroundMusic": "none" // none | upbeat | corporate | chill
},
"presetType": "sales_cloud",
"videoLength": "standard" // quick | standard | deep_dive
}Response
{
"success": true,
"narration": { "title": "...", "scenes": [...] },
"narrationTitle": "Sales Pipeline Recap — Q1 2026",
"theme": { "colors": {...}, "font": {...}, ... }
}| Status | Description |
|---|---|
| 200 | Narration generated and callback sent to Salesforce |
| 400 | No data provided or missing videoId |
| 401 | Invalid or missing API key |
| 422 | Narration produced no scenes |
| 429 | Rate limit exceeded |
| 500 | Generation failed |
Player: Get Video
/api/player/[token]Public endpoint for the animated HTML player. Fetches narration JSON and theme from Vercel Blob cache (fast) or Salesforce (fallback) using the share token. No authentication required. Tokens are 32-character hex strings (128-bit cryptographic randomness). Legacy videos without narration JSON return a videoUrl fallback.
Response (Animated Player)
{
"title": "Sales Pipeline Recap — Q1 2026",
"narration": { "title": "...", "scenes": [...] },
"theme": { "colors": {...}, "font": {...} },
"status": "complete",
"whiteLabel": false
}Response (Legacy Video)
{
"title": "Old Video Title",
"narration": null,
"theme": null,
"videoUrl": "https://example.com/video.mp4",
"legacy": true,
"status": "complete",
"whiteLabel": false
}| Status | Description |
|---|---|
| 200 | Video metadata returned |
| 400 | Invalid token format (must be 32 hex chars) |
| 404 | Video not found |
| 429 | Rate limit exceeded |
| 500 | Unable to load video |
Player: Record View
/api/player/[token]/viewIncrements the view count on a Rewind_Video__c record. Called by the player when a video starts playing. Fire-and-forget — always returns 200 even on internal errors so the player experience is unaffected.
Request Body
Empty body or no body required.
Response
{ "ok": true }| Status | Description |
|---|---|
| 200 | Always returned (check ok field for actual success) |
| 429 | Rate limit exceeded |
Player: Record Completion
/api/player/[token]/completeRecords watch analytics when a viewer finishes (or leaves) a video. Updates watch time, completion rate, and viewer country on the Rewind_Video__c record. Country is detected from Vercel's x-vercel-ip-country header.
Request Body
{
"watchTimeSeconds": 45, // Optional. Seconds watched
"completionRate": 87 // Optional. 0-100 percentage
}Response
{ "ok": true }| Status | Description |
|---|---|
| 200 | Always returned (check ok field for actual success) |
| 429 | Rate limit exceeded |
OG Image
/api/og/[token]Generates an Open Graph image (1200x630) for social media link previews. Runs on the Edge runtime. Fetches the video title from Salesforce and renders a branded preview card.
Response
Returns a image/png response (not JSON). Used by the player page's meta tags.
| Status | Description |
|---|---|
| 200 | PNG image returned |
Billing: Checkout
/api/billing/checkoutCreates a Stripe Checkout session for upgrading to a paid tier. Returns a redirect URL to the Stripe-hosted checkout page. Supports monthly and annual billing intervals. Promotion codes are enabled.
Request Body
{
"tier": "professional", // Required. starter | professional | enterprise
"interval": "monthly", // Optional. monthly (default) | annual
"orgId": "00D5f000000XXXX", // Required. Salesforce org ID
"email": "admin@example.com", // Optional. Pre-fills checkout email
"apiKey": "rw_00D5f000000XXXX_abc123..." // Required. Current API key
}Response
{
"url": "https://checkout.stripe.com/c/pay/cs_live_..."
}| Status | Description |
|---|---|
| 200 | Checkout session created |
| 400 | Invalid tier, missing orgId, missing apiKey, or invalid interval |
| 500 | Checkout failed |
Billing: Webhook
/api/billing/webhookReceives Stripe webhook events. Verified using the stripe-signature header and the STRIPE_WEBHOOK_SECRET environment variable. On successful events, syncs the updated tier and render limits back to the Salesforce Rewind_License__c record.
Handled Events
| Event | Action |
|---|---|
| checkout.session.completed | Upgrades the license tier in Salesforce, updates customer metadata in Stripe |
| customer.subscription.updated | Syncs the new tier from the subscription price to Salesforce |
| customer.subscription.deleted | Downgrades the license to free tier in Salesforce |
| Status | Description |
|---|---|
| 200 | Event received and processed (or gracefully failed) |
| 400 | Missing stripe-signature header or invalid signature |
Billing: Portal
/api/billing/portalCreates a Stripe Customer Portal session for managing an existing subscription (cancel, update payment method, view invoices). Looks up the Stripe customer by Salesforce org ID.
Request Body
{
"orgId": "00D5f000000XXXX" // Required. Salesforce org ID
}Response
{
"url": "https://billing.stripe.com/p/session/..."
}| Status | Description |
|---|---|
| 200 | Portal session created |
| 400 | Missing or invalid orgId |
| 404 | No billing account found for this org |
| 429 | Rate limit exceeded |
| 500 | Portal session failed |
Admin: List Licenses
/api/admin/licensesLists all Rewind License records from Salesforce. Requires admin authentication via the x-admin-key header. API keys are truncated in the response (first 12 characters only).
Response
{
"licenses": [
{
"id": "a0x5f000001XXXX",
"name": "RL-00001",
"apiKey": "rw_00D5f00_a1b2...",
"tier": "professional",
"isActive": true,
"rendersUsed": 12,
"rendersLimit": 75,
"nonprofitDiscount": false,
"setupComplete": true,
"created": "2026-01-15T10:00:00.000+0000",
"modified": "2026-03-01T14:30:00.000+0000"
}
],
"total": 1
}| Status | Description |
|---|---|
| 200 | License list returned |
| 401 | Invalid or missing admin key |
| 500 | Failed to list licenses |
Admin: Override License
/api/admin/overrideDirectly overrides a license tier without requiring a Stripe payment. Use for free upgrades, discounts, or revoking access. Requires admin authentication.
Request Body
{
"apiKey": "rw_00D5f00_a1b2c3d4...", // Required. Target license API key
"orgId": "00D5f000000XXXX", // Optional. Falls back to stored org ID
"tier": "enterprise", // Optional. Defaults to "free"
"rendersLimit": 9999, // Optional. Defaults to tier's standard limit
"isActive": true, // Optional. Enable/disable the license
"reason": "Partner comp" // Optional. Audit note (logged server-side)
}Response
{
"success": true,
"licenseId": "a0x5f000001XXXX",
"previousTier": "free",
"newTier": "enterprise",
"rendersLimit": 9999,
"reason": "Partner comp"
}| Status | Description |
|---|---|
| 200 | Override applied |
| 400 | Missing apiKey, invalid API key format, or invalid tier |
| 401 | Invalid or missing admin key |
| 404 | License not found for the given API key |
| 429 | Rate limit exceeded |
| 500 | Override failed |
Tier Configuration
These are the render limits enforced by the API per billing tier:
| Tier | Renders / Month | Notes |
|---|---|---|
| free | 3 | Default tier for all new registrations |
| starter | 15 | Scheduling + custom branding |
| professional | 75 | Email delivery + up to 10 concurrent |
| enterprise | Unlimited (9999) | White-label player + analytics dashboard |