Leads API
Your website tells Vidrip who just signed up. Someone on your team records a short welcome video for that person from the Vidrip app, they get a branded email, and they can reply on the spot. This API is how the signups get in, and how you read back what happened to each one.
Everything is JSON over HTTPS. Timestamps are ISO 8601 in UTC. Responses are UTF-8 with content-type: application/json.
Authentication
Every request carries a community API key. Keys start with vk_live_ and are created by a community owner or admin at vidrip.app/account → Communities → Website connection. A key belongs to exactly one community and can be revoked at any time.
Authorization: Bearer vk_live_…
# or
x-api-key: vk_live_…Keep keys server-side. A key can create, read, update and delete that community's leads, so it should never ship in a browser bundle or a mobile app.
The lead object
What every read returns. A lead is one person who signed up on your site.
{
"id": "8d2a4c9e-3f7b-4b1e-9c2d-1a2b3c4d5e6f",
"kind": "signup",
"name": "Ana Lima",
"email": "[email protected]",
"meta": { "plan": "pro", "source": "pricing-page" },
"idempotency_key": "email:[email protected]",
"status": "sent",
"created_at": "2026-09-18T14:02:11.000Z",
"welcomed_at": "2026-09-18T14:19:40.000Z",
"welcome": {
"url": "https://vidrip.app/o/Xk3pQ9vT2mLa",
"opened_at": "2026-09-18T15:01:02.000Z",
"replied_at": null
}
}| Field | Type | Notes | |
|---|---|---|---|
id | uuid | always | Stable id for the lead. Use it for reads, updates and deletes. |
kind | string | always | What happened on your side. Default "signup". Free text up to 40 characters, e.g. "trial", "waitlist". |
name | string | null | always | The person’s name if you sent one. First name is used in the welcome script. |
email | string | always | Lowercased. Where the welcome email goes. |
meta | object | always | Whatever you sent. Shown to the team. "source" or "utm_source" also feeds the dashboard’s source breakdown. |
idempotency_key | string | always | Yours, or "email:<lowercased email>" by default. |
status | enum | always | open · claimed · sent · dismissed. See below. |
created_at | timestamp | always | When the lead was created. |
welcomed_at | timestamp | null | always | When the welcome video and email were sent. |
welcome | object | null | always | Present once welcomed: the page the person received, when they first opened it, and when they first replied. |
Status values
| Field | Type | Notes | |
|---|---|---|---|
open | status | Waiting for someone on the team to pick it up. | |
claimed | status | A team member is recording the welcome right now (a 15-minute hold). Reads as open again if it lapses. | |
sent | status | The welcome video and email went out. welcome.url is live. | |
dismissed | status | Retired from active views, by the team or by you. A lead that was already welcomed keeps welcomed_at and welcome. Final. |
Create a lead
/eventsCall this the moment someone signs up. It returns 202 Accepted immediately; the welcome happens later, when a team member records it.
| Field | Type | Notes | |
|---|---|---|---|
email | string | required | A valid address. Lowercased on the way in. |
name | string | optional | Up to 80 characters. Without it the team sees the email and the script opens with "Hey there". |
kind | string | optional | Default "signup". |
idempotency_key | string | optional | Up to 200 characters. Default is the email, so the same person never queues twice. Send your own user id to allow a second welcome for a changed address. |
meta | object | optional | Up to 4,000 characters of JSON. Anything useful to the person welcoming them. |
// 202
{
"ok": true,
"event_id": "8d2a4c9e-…",
"status": "open", // or "unsubscribed" (event_id null): accepted, nothing queued
"duplicate": false, // true when the idempotency key already existed — the existing lead is returned
"pushed": 3, // team members notified
"lead": { …the lead object… }
}Create many
/events/batchUp to 100 leads in one call, for imports and backfills. Each item is validated and created exactly like a single create, with the same idempotency. The team gets one push for the whole batch; pass "notify": false for none.
{ "leads": [ { "email": "[email protected]", "name": "Ana Lima" }, { "email": "[email protected]" } ], "notify": true }
// 202
{ "ok": true, "created": 2, "duplicates": 0, "unsubscribed": 0, "failed": 0,
"results": [ { "index": 0, "ok": true, "event_id": "…", "status": "open", "duplicate": false }, … ] }ok is false when any item failed; the per-item results say which and why. Valid items are still created.
List leads
/eventsNewest first, 50 per page by default. Use next from one page as before on the next.
| Field | Type | Notes | |
|---|---|---|---|
status | enum | optional | all (default) · open · claimed · sent · dismissed |
limit | number | optional | 1 to 200. Default 50. |
before | timestamp | optional | Only leads created before this time. Pass the previous page’s next. |
q | string | optional | Case-insensitive match on email or name. |
GET /events?status=sent&limit=100
// 200
{ "leads": [ { …lead… }, … ], "next": "2026-09-12T09:31:00.000Z" } // next is null on the last pageGet a lead
/events/:id// 200
{ "lead": { …lead… } }Poll this, or the list filtered by status=sent, to sync welcome status and replies back into your CRM. A lead from another community answers 404.
Update a lead
/events/:idFix a name, change the address before the welcome goes out, or attach more context. Only the fields you send change.
| Field | Type | Notes | |
|---|---|---|---|
name | string | null | optional | Up to 80 characters. |
email | string | optional | Allowed while the lead is open, claimed or dismissed. Once welcomed the email already went out, so this answers 409. |
kind | string | optional | |
meta | object | optional | Replaces meta entirely. |
{ "name": "Ana L.", "meta": { "plan": "team" } }
// 200
{ "lead": { …lead… } }Delete a lead
/events/:idRemoves the lead outright: a mistaken signup, a test, a request to be forgotten. Allowed for open, claimed and dismissed leads. Once welcomed, the video and the reply thread exist, so a welcomed lead answers 409 — dismiss it instead, which always succeeds.
// 200
{ "ok": true }Dismiss a lead
/events/:idTake a lead out of active views without deleting it. Works at any stage, including after the welcome: the record stays, marked dismissed, and the welcome video and reply thread are preserved. Use it to retire test leads and people who cancelled. Dismissing an already-dismissed lead returns 200 with the lead unchanged.
{ "action": "dismiss" }
// 200
{ "lead": { …lead, "status": "dismissed" } }What happens next
- Every team member gets a push. The lead appears in their queue in the Vidrip app.
- One of them claims it and records a short welcome on camera, with a teleprompter built from your community's template and the person's first name.
- The person receives the branded welcome email from your community's address or from Vidrip. The lead's status becomes
sentandwelcome.urlis live. - They watch on the page and reply, in text or on video. The first reply sets
welcome.replied_atand the team gets a push. - If they sign up or log in on the page, they connect with your community, and your team can message them in the app from then on.
Limits and idempotency
- Idempotent by email. The same idempotency key never creates a second lead in a community. A repeat returns the existing lead with
duplicate: trueand nobody is notified again. Retrying a failed request is always safe. - Unsubscribes are honoured for you. Every welcome email carries a one-click unsubscribe. A lead for an unsubscribed address is accepted and skipped with
status: "unsubscribed". - 500 new leads per community per day. Above that the API answers 429. Ask us if you need more.
- 100 leads per batch call. 200 per list page.
Errors
Errors are JSON with a single human-readable error field.
| Field | Type | Notes | |
|---|---|---|---|
400 | status | Validation: a missing or invalid email, oversized meta, an unknown status filter. | |
401 | status | Missing, invalid or revoked API key. | |
403 | status | The key belongs to another community, or the action is not allowed with a key. | |
404 | status | No such lead in this community. | |
409 | status | Conflict: changing the email of a welcomed lead, deleting a welcomed lead (dismiss instead), or an email another lead already has. Dismissal never returns 409. | |
429 | status | Daily limit reached. |
{ "error": "a valid email is required" }Examples
curl
curl -X POST https://vidrip.app/api/outreach/events \
-H "Authorization: Bearer $VIDRIP_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Ana Lima", "email": "[email protected]", "meta": { "plan": "pro", "source": "pricing-page" } }'Node
const res = await fetch('https://vidrip.app/api/outreach/events', {
method: 'POST',
headers: { authorization: `Bearer ${process.env.VIDRIP_KEY}`, 'content-type': 'application/json' },
body: JSON.stringify({ name: user.name, email: user.email, idempotency_key: `user:${user.id}`, meta: { plan: user.plan } }),
});
const { event_id, duplicate } = await res.json();Sync welcome status back (Node)
let before = null;
do {
const url = new URL('https://vidrip.app/api/outreach/events'); url.searchParams.set('status', 'sent'); url.searchParams.set('limit', '200');
if (before) url.searchParams.set('before', before);
const { leads, next } = await (await fetch(url, { headers: { authorization: `Bearer ${process.env.VIDRIP_KEY}` } })).json();
for (const lead of leads) crm.update(lead.email, { welcomedAt: lead.welcomed_at, repliedAt: lead.welcome?.replied_at ?? null });
before = next;
} while (before);Python
import os, requests
r = requests.post("https://vidrip.app/api/outreach/events",
headers={"Authorization": f"Bearer {os.environ['VIDRIP_KEY']}"},
json={"name": "Ana Lima", "email": "[email protected]", "meta": {"plan": "pro"}})
print(r.status_code, r.json()) # 202 {'ok': True, 'event_id': '…', 'status': 'open', ...}