{"openapi":"3.1.0","info":{"title":"RavePilot API","version":"1.0.0","description":"Create testimonial invitations, read submissions and clips, and manage HMAC-signed webhooks. A customer is identified by EMAIL ADDRESS; invitations are idempotent per (lowercased) email within the workspace. Human docs with a live console: https://ravepilot.com/docs","contact":{"url":"https://ravepilot.com/docs"}},"servers":[{"url":"https://api.ravepilot.com"}],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"tags":[{"name":"Invitations","description":"Start the testimonial loop for a customer."},{"name":"Submissions","description":"Read recording sessions, clips, and review state."},{"name":"Webhooks","description":"Manage signed event delivery to your endpoints."}],"paths":{"/v1/invitations":{"post":{"tags":["Invitations"],"operationId":"createInvitation","summary":"Create an invitation (idempotent per email)","description":"Creates (or returns the existing) invitation for a customer and, by default, emails them their personal recording link on your branded portal. Idempotent per lowercased email within the workspace. Requires the `invitations` scope. Fires the `invitation.created` webhook event.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationRequest"}}}},"responses":{"200":{"description":"Invitation created or reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/magic-link":{"post":{"tags":["Invitations"],"operationId":"createMagicLink","summary":"Mint a recording link without sending email","description":"Same creator as `/v1/invitations` but never sends the email — for embedding \"record a testimonial\" inside your own app or CRM. Requires the `invitations` scope.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkRequest"}}}},"responses":{"200":{"description":"Link minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/submissions":{"get":{"tags":["Submissions"],"operationId":"listSubmissions","summary":"List submissions","description":"Workspace submissions, newest first. Requires the `submissions` scope.","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/SubmissionStatus"},"description":"Filter by lifecycle status."},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Submissions","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"submissions":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/submissions/{id}":{"get":{"tags":["Submissions"],"operationId":"getSubmission","summary":"Get a submission with its clips","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"sub_a1b2c3"}],"responses":{"200":{"description":"Submission detail","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"submission":{"$ref":"#/components/schemas/Submission"},"clips":{"type":"array","items":{"$ref":"#/components/schemas/Clip"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"operationId":"listWebhooks","summary":"List webhook endpoints (and valid event names)","responses":{"200":{"description":"Endpoints","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"events":{"type":"array","items":{"type":"string"}},"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Webhooks"],"operationId":"createWebhook","summary":"Register a webhook endpoint","description":"The signing `secret` (whsec_…) is returned ONCE in this response — store it to verify deliveries.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Must be https://","example":"https://example.com/ravepilot-webhook"},"events":{"type":"array","items":{"type":"string"},"description":"Event names from the catalog, or [\"*\"] (default).","example":["clip.approved","render.succeeded"]}}}}}},"responses":{"200":{"description":"Endpoint created — secret shown once","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string","example":"wh_a1b2"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"secret":{"type":"string","example":"whsec_…","description":"HMAC-SHA256 signing secret. Shown only here."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/webhooks/{id}":{"delete":{"tags":["Webhooks"],"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"wh_a1b2"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deleted":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/webhook-deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"Recent delivery log (last 50)","description":"Per-delivery status, attempts, and your endpoint's response code. Retries back off 1m → 5m → 30m → 2h → 6h (5 attempts); an endpoint is auto-disabled after 25 consecutive failures.","responses":{"200":{"description":"Deliveries","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"webhooks":{"invitation.created":{"post":{"summary":"An invitation was minted (API or portal)","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"submission.submitted":{"post":{"summary":"The customer finished and submitted their session","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"clip.uploaded":{"post":{"summary":"A clip finished uploading and encoding","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"clip.approved":{"post":{"summary":"A clip cleared review","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"clip.rejected":{"post":{"summary":"A clip was rejected with reviewer notes","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"submission.approved":{"post":{"summary":"The whole session was approved; rewards fire","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"render.succeeded":{"post":{"summary":"An ad-ready marketing cut finished rendering","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"payout.sent":{"post":{"summary":"The customer reward was sent","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"payout.delivered":{"post":{"summary":"The customer claimed their reward","requestBody":{"$ref":"#/components/requestBodies/Event"}}},"payout.failed":{"post":{"summary":"A reward failed","requestBody":{"$ref":"#/components/requestBodies/Event"}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rp_live_…","description":"Workspace API key as a bearer token."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Workspace API key as a header (equivalent to bearer)."}},"requestBodies":{"Event":{"description":"Signed event delivery. Headers: X-RavePilot-Event (name), X-RavePilot-Delivery (unique id, use for idempotency), X-RavePilot-Signature: t=<unix>,v1=<hex> where v1 = HMAC_SHA256(secret, t + \".\" + rawBody). Verify over the raw body, compare in constant time, reject |now - t| > 300s.","content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","example":"clip.approved"},"created_at":{"type":"integer","description":"Unix seconds"},"data":{"type":"object","description":"Event-specific payload (submission/clip/render/payout fields)."}}}}}}},"responses":{"Unauthorized":{"description":"Missing/malformed key or wrong scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Validation problem — error string says what to fix","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found in this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"status":{"type":"integer"}},"example":{"error":"missing or malformed API key","status":401}},"SubmissionStatus":{"type":"string","enum":["in_progress","submitted","approved","partial_approved","rejected","paid"],"description":"Lifecycle: in_progress → submitted → approved | partial_approved | rejected → paid"},"InvitationRequest":{"type":"object","required":["customer_email"],"properties":{"customer_email":{"type":"string","format":"email","description":"The customer's email — their stable identity. Invitations are idempotent per lowercased email.","example":"marjorie@acme.com"},"customer_name":{"type":"string","example":"Marjorie Ellison"},"customer_phone":{"type":"string","description":"Optional metadata only (10-digit North American). Phone-based identity / third-party auth is planned for the Enterprise plan."},"send_email":{"type":"boolean","default":true},"collector_id":{"type":"string","description":"Target collector (campaign); defaults to the workspace primary."}}},"MagicLinkRequest":{"type":"object","required":["customer_email"],"properties":{"customer_email":{"type":"string","format":"email","example":"marjorie@acme.com"},"customer_name":{"type":"string"},"customer_phone":{"type":"string","description":"Optional metadata only."},"collector_id":{"type":"string"}}},"InvitationResponse":{"type":"object","properties":{"ok":{"type":"boolean"},"reused":{"type":"boolean","description":"True when an existing invitation for this email was returned."},"link":{"type":"string","format":"uri","description":"The customer's branded recording URL."},"invite_token":{"type":"string"},"submission":{"$ref":"#/components/schemas/Submission"}}},"Submission":{"type":"object","properties":{"id":{"type":"string","example":"sub_a1b2c3"},"status":{"$ref":"#/components/schemas/SubmissionStatus"},"customer_phone":{"type":"string","nullable":true},"customer_email":{"type":"string","nullable":true},"customer_name":{"type":"string","nullable":true},"hubspot_contact_id":{"type":"string","nullable":true},"created_at":{"type":"integer","description":"Unix seconds"},"submitted_at":{"type":"integer","nullable":true},"approved_at":{"type":"integer","nullable":true}}},"Clip":{"type":"object","properties":{"id":{"type":"string","example":"clip_a1b2"},"script_key":{"type":"string","description":"Which script the customer answered."},"status":{"type":"string","enum":["recording","uploaded","approved","rejected","redo_requested"]},"duration_seconds":{"type":"number","nullable":true},"transcript":{"type":"string","nullable":true},"stream_video_uid":{"type":"string","nullable":true,"description":"Cloudflare Stream UID for playback/download."},"used_in_marketing":{"type":"boolean"},"created_at":{"type":"integer"}}},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"},"created_at":{"type":"integer"},"disabled_at":{"type":"integer","nullable":true}}},"Delivery":{"type":"object","properties":{"id":{"type":"string"},"webhook_id":{"type":"string"},"event":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"created_at":{"type":"integer"},"delivered_at":{"type":"integer","nullable":true}}}}}}