The fallo API
Read your fans and how your videos did, add fans from other tools, and get told the moment a fan joins or a video goes out. JSON in and out, one key per tool. No code? Use Zapier.
Quickstart: 2 minutes
From nothing to a working key, a new fan and a webhook.
-
Make a key
In fallo, open Integrations and press Make a key. Copy it straight away: it starts with
fl_live_and fallo shows it only once. -
Check it works
curl https://fallo.ai/api/v1/me \ -H "Authorization: Bearer fl_live_your_key"You get your channel back:
{ "channel": { "name": "Ana Cooks", "handle": "@anacooks", "fan_page": "https://fallo.ai/anacooks", "verified": true }, "fans": 1284, "key": "My script" } -
Add a fan
curl -X POST https://fallo.ai/api/v1/fans \ -H "Authorization: Bearer fl_live_your_key" \ -H "Content-Type: application/json" \ -d '{"email": "sam@example.com", "source": "link"}'fallo emails Sam a confirm link. Sam is
pendinguntil they tap it, thenactive. -
Get told when fans join
curl -X POST https://fallo.ai/api/v1/hooks \ -H "Authorization: Bearer fl_live_your_key" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/fallo-hook", "event": "fan.joined"}'Keep the
secretin the answer: you’ll use it to check each call is from fallo. You can also add webhooks on Integrations.
Zapier (no code)
Don’t want to write code? The fallo app for Zapier connects fallo to thousands of apps: Google Sheets, Mailchimp, Discord, Slack and the rest.
Pick a fallo trigger, pick an app, and switch the Zap on. Zapier asks for an API key from Integrations.
Use fallo in ZapierMore ideas on the Integrations page.
Authentication
Send your key on every request in the Authorization header (or as X-API-Key if your tool can’t set that header):
Authorization: Bearer fl_live_your_key - A key is
fl_live_plus 40 characters. fallo keeps only a fingerprint of it, so a lost key can’t be shown again: make a new one. - A key only sees its own channel.
- Revoke a key on Integrations any time. It stops working at once, and webhooks made with it stop too.
Endpoints
Base URL: https://fallo.ai/api/v1. JSON in and out. Dates are UTC, in ISO 8601 (2026-10-04T15:02:11Z).
/meThe channel your key belongs to, your active fan count and the key’s name. Use it to check a key works./fansYour fans, newest first. Filters: status (active, pending, unsubscribed, bounced, removed), since (a date), limit (1–100, default 50). For the next page, pass cursor = the next_cursor you got (it’s null on the last page)./fans/{id or email}One fan./fansAdd a fan: {"email": "…", "source": "link"}. They get the confirm email and join when they tap it. Answers 201 for someone new, 200 if they were already on your list. Your channel must be verified, and fallo’s spam limits apply./fans/{id or email}Stop emails to a fan (their status becomes removed, and fan.left fires)./videosYour videos, newest first, with how many fans each was sent to, opened and watched. limit 1–100, default 20./statsTotals: active and pending fans, fans who joined in the last 7 days, emails sent and opened in the last 30 days, videos sent./hooksYour webhooks./hooksSubscribe a URL to an event (REST hooks, as Zapier uses): {"url": "https://…", "event": "fan.joined"}. The answer includes the webhook’s secret./hooks/{id}Unsubscribe a webhook.The fan object
Lists come back as {"data": [ … ], "next_cursor": …}, one fan as {"data": { … }}. A fan looks like this:
{
"id": "a1b2c3d4e5f6",
"email": "sam@example.com",
"status": "active",
"source": "qr",
"source_label": "QR code",
"joined_at": "2026-10-04T15:02:11Z",
"confirmed_at": "2026-10-04T15:03:40Z"
} - id: fallo’s id for the fan. Use it or their email in
/fans/{id or email}. - status:
pending(hasn’t confirmed yet),active,unsubscribed,bouncedorremoved. - source: where they came from, e.g.
link,qr,desc,comment,post,bio,chat,social. When you add a fan, anything else becomeslink. source_label is the same in words. - confirmed_at is
nulluntil they tap the confirm link.
POST /fans also tells you confirm_email_sent (true or false) and a note in plain words.
Errors
Errors come back with a matching HTTP status and a body like this:
{
"error": {
"code": "bad_email",
"message": "email must be a valid email address."
}
} | Status | Code | What it means |
|---|---|---|
| 400 | bad_email bad_status bad_since bad_event bad_url | Something in the request isn’t right. The message says what. |
| 401 | unauthorized | No key, or the key is wrong or revoked. |
| 403 | not_verified | Verify your channel on fallo before adding fans. |
| 404 | not_found | No such fan, or no such endpoint. |
| 429 | rate_limited | Over 120 requests a minute. Wait for Retry-After seconds. |
| 429 | not_sent | fallo’s spam limits held back the confirm email. The message says why; try again later. |
Rate limit
Up to 120 requests a minute per key. Over that, you get 429 with Retry-After: 60. Each key has its own limit.
Webhooks
Add a webhook on Integrations, or with POST /hooks, and fallo will POST to it when:
The body is JSON. data is a fan, or for video.sent a video:
{
"event": "fan.joined",
"created_at": "2026-10-04T15:03:40Z",
"data": {
"id": "a1b2c3d4e5f6",
"email": "sam@example.com",
"status": "active",
"source": "qr",
"source_label": "QR code",
"joined_at": "2026-10-04T15:02:11Z",
"confirmed_at": "2026-10-04T15:03:40Z"
}
} {
"event": "video.sent",
"created_at": "2026-10-04T16:05:12Z",
"data": {
"id": "dQw4w9WgXcQ",
"title": "My studio tour",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"kind": "upload",
"status": "sent",
"published_at": "2026-10-04T16:02:00Z",
"sent_at": "2026-10-04T16:05:12Z",
"sent_to": 1284,
"opened": 0,
"watched": 0
}
} - Headers:
X-Fallo-Signature(see below),X-Fallo-Event-Id(the same on every retry of one event) andUser-Agent: Fallo-Webhooks/1. - Answer with any
2xx. If your side is down, fallo retries after 1, 5 and 30 minutes, 2 hours and 12 hours, then gives up on that event. - Answer
410to unsubscribe. A webhook that fails 50 times in a row is switched off. - Webhook addresses must be public
https://URLs. Up to 25 per channel. - Send a test on Integrations posts a sample with
"test": true.
Checking signatures
Every call carries X-Fallo-Signature: sha256=…, an HMAC-SHA256 of the raw body with the webhook’s secret (from POST /hooks). Check it before trusting the call, using the raw body, not re-encoded JSON:
// Node (with Express, use express.raw({ type: 'application/json' }) so rawBody is the exact bytes)
const want = Buffer.from('sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'));
const got = Buffer.from(req.get('X-Fallo-Signature') || '');
if (got.length !== want.length || !crypto.timingSafeEqual(got, want)) return res.sendStatus(401); // PHP
$rawBody = file_get_contents('php://input');
$sig = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($sig, $_SERVER['HTTP_X_FALLO_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
Good to know
- Fans added through the API still confirm by email. fallo never emails anyone who didn’t say yes.
- Keys can be revoked any time on Integrations; webhooks made with that key stop too.
- Questions: hello@fallo.ai