Quickstart
Three calls take you from nothing to a finished, branded testimonial landing in your webhook handler:
1 — Create an invitation. Pass a customer's phone number; RavePilot resolves the contact, mints their personal recording link on your branded portal, and (by default) emails it to them.
2 — Listen. Register a webhook endpoint once. You'll hear clip.uploaded when they record, clip.approved when the clip clears review, and render.succeeded when the ad-ready cut is finished.
3 — Read back. Pull GET /v1/submissions any time for the full state — clips, transcripts, and review status.
# create an invitation (emails the customer their recording link) curl https://api.ravepilot.com/v1/invitations \ -H "Authorization: Bearer rp_live_…" \ -H "Content-Type: application/json" \ -d '{"customer_email": "marjorie@acme.com", "customer_name": "Marjorie Ellison"}'
Authentication
Every request carries a workspace API key — either as a bearer token or an X-API-Key header. Keys are scoped (invitations, submissions, webhooks) and shown once at creation; RavePilot stores only a hash.
Authorization: Bearer rp_live_a1b2c3…
# or equivalently
X-API-Key: rp_live_a1b2c3…
Invitations
An invitation creates (or reuses) a submission — the customer's recording session — and returns their personal link on your branded portal. Invitations are idempotent per email number: the same customer always gets the same link.
Create an invitation and, by default, email the customer their recording link. The customer's email is their identity: invitations are idempotent per lowercased email within your workspace, so the same customer always gets the same link and re-posting never creates duplicates. Requires the invitations scope.
| Field | Description |
|---|---|
| customer_emailREQUIRED | The customer's email address — their stable identity, and where the invitation goes. |
| customer_nameoptional | Used in the recording flow and the lower-third on rendered ads. |
| customer_phoneoptional | Stored as metadata only (10-digit North American number). |
| send_emaildefault true | Set false to mint the link without sending anything. |
| collector_idoptional | Target a specific collector (campaign). Defaults to your workspace's primary collector. |
Returns { ok, link, invite_token, submission, reused? } — link is the customer-facing recording URL on your branded host. Fires invitation.created.
The same creator, but it never sends email — for embedding "record a testimonial" inside your own app, portal, or CRM. Same body as create an invitation minus the email behavior.
Submissions
A submission is one customer's recording session: their consent, their clips, and where they sit in review. Status moves through:
List your workspace's submissions, newest first. Requires the submissions scope.
| Query | Description |
|---|---|
| statusoptional | Filter by lifecycle status (see above). |
| limitdefault 50, max 200 | Page size. |
| offsetdefault 0 | Pagination offset. |
One submission with its clips — script keys, durations, transcripts, review status, and playback handles.
Webhooks
Register an HTTPS endpoint and RavePilot pushes the pipeline to you. Every delivery is signed, logged, and retried with backoff.
Event catalog
Register an endpoint. Requires the webhooks scope.
| Field | Description |
|---|---|
| urlREQUIRED | Your HTTPS endpoint. |
| eventsdefault ["*"] | Array of event names from the catalog, or "*" for everything. |
whsec_… in the create response. Store it; you'll need it to verify signatures.Your registered endpoints, plus the full list of valid event names.
Remove an endpoint. Pending deliveries to it are dropped.
The 50 most recent deliveries with status, attempt count, and your endpoint's response code — your first stop when debugging a handler.
Failed deliveries retry on a backoff of 1m → 5m → 30m → 2h → 6h (5 attempts). After 25 consecutive failures an endpoint is automatically disabled; recreate it when your handler is healthy again.
Verifying signatures
Every delivery carries three headers:
| Header | Description |
|---|---|
| X-RavePilot-Event | The event name, e.g. clip.approved. |
| X-RavePilot-Delivery | Unique delivery id — use it for idempotency. |
| X-RavePilot-Signature | t=<unix>,v1=<hex> where v1 = HMAC_SHA256(secret, t + "." + rawBody). |
Compute the HMAC over the raw request body, compare in constant time, and reject stale timestamps (5 minutes is a sane window):
import crypto from 'node:crypto'; function verify(rawBody, signatureHeader, secret) { const parts = Object.fromEntries(signatureHeader.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 || '')); }
import hmac, hashlib, time def verify(raw_body: bytes, signature_header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in signature_header.split(",")) expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest() fresh = abs(time.time() - int(parts["t"])) < 300 return fresh and hmac.compare_digest(expected, parts.get("v1", ""))
Errors
Errors are JSON: { "error": "human-readable reason", "status": 4xx }. The shapes you'll meet:
| Status | Meaning |
|---|---|
| 401 | Missing or malformed API key. Keys start with rp_. |
| 403 | Valid key, wrong scope — check the scope each endpoint requires. |
| 404 | The resource doesn't exist in your workspace. |
| 400 / 409 | Validation problems — the error string says exactly what to fix. |
| 5xx | Our side. Retry with backoff; side-effecting calls are idempotent per email. |