Developers

The RavePilot API

Create testimonial invitations from your own app or CRM, read submissions and clips, and react to the pipeline with signed webhooks. Every endpoint on this page is runnable — paste a key and press Run.

Base URL https://api.ravepilot.com Auth Authorization: Bearer rp_live_… OpenAPI openapi.json LLMs docs.md
Quickstart Auth Invitations Submissions Webhooks Signatures Errors

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…
Where do keys come from? Your workspace owner mints them in the admin portal at app.ravepilot.com. Each key belongs to one workspace; everything it touches is scoped to that workspace's data.

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.

POST /v1/invitations

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.

FieldDescription
customer_emailREQUIREDThe customer's email address — their stable identity, and where the invitation goes.
customer_nameoptionalUsed in the recording flow and the lower-third on rendered ads.
customer_phoneoptionalStored as metadata only (10-digit North American number).
send_emaildefault trueSet false to mint the link without sending anything.
collector_idoptionalTarget 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.

Phone-number-based customer identity and third-party authentication are planned for the Enterprise plan. Today, email is the identity everywhere — the same input the hosted landing page takes.

Submissions

A submission is one customer's recording session: their consent, their clips, and where they sit in review. Status moves through:

in_progress submitted approved / partial_approved / rejected paid
GET /v1/submissions

List your workspace's submissions, newest first. Requires the submissions scope.

QueryDescription
statusoptionalFilter by lifecycle status (see above).
limitdefault 50, max 200Page size.
offsetdefault 0Pagination offset.
GET /v1/submissions/:id

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

invitation.createdAn invitation was minted (API or portal).
submission.submittedThe customer finished and submitted their session.
clip.uploadedA clip finished uploading and encoding.
clip.approvedA clip cleared review.
clip.rejectedA clip was rejected with reviewer notes.
submission.approvedThe whole session was approved; rewards fire.
render.succeededAn ad-ready marketing cut finished rendering.
payout.sentThe customer reward was sent.
payout.deliveredThe customer claimed their reward.
payout.failedA reward failed — investigate and retrigger.
POST /v1/webhooks

Register an endpoint. Requires the webhooks scope.

FieldDescription
urlREQUIREDYour HTTPS endpoint.
eventsdefault ["*"]Array of event names from the catalog, or "*" for everything.
The signing secret is returned oncewhsec_… in the create response. Store it; you'll need it to verify signatures.
GET /v1/webhooks

Your registered endpoints, plus the full list of valid event names.

DELETE /v1/webhooks/:id

Remove an endpoint. Pending deliveries to it are dropped.

GET /v1/webhook-deliveries

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:

HeaderDescription
X-RavePilot-EventThe event name, e.g. clip.approved.
X-RavePilot-DeliveryUnique delivery id — use it for idempotency.
X-RavePilot-Signaturet=<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 || ''));
}

Errors

Errors are JSON: { "error": "human-readable reason", "status": 4xx }. The shapes you'll meet:

StatusMeaning
401Missing or malformed API key. Keys start with rp_.
403Valid key, wrong scope — check the scope each endpoint requires.
404The resource doesn't exist in your workspace.
400 / 409Validation problems — the error string says exactly what to fix.
5xxOur side. Retry with backoff; side-effecting calls are idempotent per email.