01Authentication
Every request must include an API key. Keys are Pro-plan and above; Free and Starter accounts see the API-key card in Settings with an upgrade note. Keys are one per use: the plaintext is shown once at creation — copy it into 1Password / your secrets manager immediately.
Preferred header: X-API-Key: bk_live_… — this is Zapier's convention and keeps the key out of the Authorization field where some log scrubbers miss it.
Also accepted: Authorization: Bearer bk_live_… for clients that prefer standard bearer auth.
# Quick sanity check — this should return an array (possibly empty)
curl -H "X-API-Key: bk_live_XXXX" \
https://bioleads.link/api/v1/leadsWhere to find your key: log in → click your avatar (top-right) → Settings → API keys. Click Create, copy the shown key immediately. Two buttons appear on every active key — Regenerate (rotates in place, old key stops working now) and Revoke (permanent).
02GET /api/v1/leads
Returns the caller's most recent leads as a flat JSON array, newest first. Every key is present on every item — nulls are used when a field is missing, so your schema-based parsers never break.
Query parameters
limit— integer, default25, max100.page_id— optional integer. Filter to leads captured by a specific bio-page. Get bio-page IDs from Dashboard → Bio pages.
Example
curl -H "X-API-Key: bk_live_XXXX" \
"https://bioleads.link/api/v1/leads?limit=3"Response — 200 OK
[
{
"id": "lead_92",
"created_at": "2026-07-30T10:40:10.976329Z",
"page_id": "13",
"page_name": "Ravi Mandanka",
"name": "Zapier Smoke Person",
"email": "zapier-smoke@example.com",
"phone": "+15550001234",
"source": "zapier-smoke",
"answers": [
{ "question": "Reason", "answer": "End-to-end dispatch test" }
]
}
]Field reference
id(string) — stable unique lead id in the formlead_<n>. Use as your deduplication key. Never changes.created_at(string) — ISO 8601 UTC timestamp with trailingZ.page_id(string) — bio-page identifier. String, not int, for Zapier field-picker stability.page_name(string) — human name of the bio page the lead was captured on.name,email,phone(string | null) — auto-detected from the form field labels. Nullable when the visitor didn't provide them or the form didn't ask.source(string | null) — traffic source (Instagram, Direct, TikTok, …). Auto-classified from the referrer and UTM params at capture time.answers(array of { question, answer }) — every other filled form field, in the order the form defines them.questionis the form field's label;answeris the visitor's value. Empty array when no additional questions were asked.
Empty result: [] with HTTP 200. Never 404 — an empty inbox is a valid state.
03POST /api/v1/hooks
Register a webhook subscription. When a new lead is captured on any of your bio pages, the payload matching a single item from GET /api/v1/leads is POSTed to your target_url.
Request body
{
"target_url": "https://your-server.example/hooks/bioleads",
"event": "new_lead"
}target_url— required, must start withhttps://. HTTP is rejected. URLs resolving to loopback / private / cloud-metadata IPs are rejected as an SSRF defense.event— required. Currently only"new_lead"is supported; more events will be added and returned as valid values here.
Example
curl -X POST \
-H "X-API-Key: bk_live_XXXX" \
-H "Content-Type: application/json" \
-d '{"target_url":"https://your-server.example/hooks","event":"new_lead"}' \
https://bioleads.link/api/v1/hooksResponse
New subscription: 201 Created with { "id": 42 }.
Idempotency — if you POST the same (target_url, event) pair twice, the second call returns 200 OK with the same id. Safe to call repeatedly on Zap-on / connection re-establish. If the subscription was previously auto-disabled by a 410 Gone, the repeat POST re-enables it.
# First call — 201 Created
$ curl ... /api/v1/hooks
{"id":42}
# Same body again — 200 OK, same id
$ curl ... /api/v1/hooks
{"id":42}04DELETE /api/v1/hooks/{id}
Unsubscribe. Idempotent — deleting an unknown or already-deleted id returns 204 No Content. This lets Zapier (and your own retry logic) call DELETE safely on Zap-off without special-casing 404.
curl -X DELETE \
-H "X-API-Key: bk_live_XXXX" \
https://bioleads.link/api/v1/hooks/42Response: 204 No Content, empty body, whether or not the id existed.
05Webhook delivery
When a new lead is captured, we POST to every active subscription for that account. The payload is byte-identical to one item from GET /api/v1/leads:
POST /your/hook/endpoint HTTP/1.1
Content-Type: application/json
X-BioLeads-Event: lead.created
{
"id": "lead_92",
"created_at": "2026-07-30T10:40:10.976329Z",
"page_id": "13",
"page_name": "Ravi Mandanka",
"name": "Zapier Smoke Person",
"email": "zapier-smoke@example.com",
"phone": "+15550001234",
"source": "zapier-smoke",
"answers": [
{ "question": "Reason", "answer": "End-to-end dispatch test" }
]
}Retry behavior
We attempt delivery up to 4 times per lead. Between attempts we sleep for a fixed backoff schedule:
- Attempt 1 — immediate on lead creation.
- Attempt 2 — 1 second sleep, then send, if attempt 1 failed.
- Attempt 3 — 5 second sleep, then send.
- Attempt 4 — 30 second sleep, then send. Then we give up.
Success (any 2xx) stops the loop immediately.
Timeout
Each attempt times out after 10 seconds. A hung endpoint counts as a failed attempt and moves to the next backoff.
Total delivery window
Combining the two: the sleep schedule totals 36 seconds, and each of the 4 attempts can spend up to 10 seconds on the wire.
- Fast-fail endpoint (immediate 4xx/5xx): ~36–40 s from lead creation to final give-up.
- Slow / hung endpoint (every attempt hits the 10 s timeout): up to ~76 s. Alerting rules should key off this ceiling, not the fast-fail number.
How to acknowledge
Respond with any 2xx status. Body content is ignored — a bare 200 OK with empty body is enough.
How to unsubscribe from our side
Respond with HTTP 410 Gone. We immediately mark the subscription inactive and stop delivering. Do this when a Zap is deleted upstream, the endpoint moves, or the user disconnects the integration in your product. 410 is the ONLY status that auto-disables — any other error keeps retrying to protect against transient outages.
06Errors
All errors return a JSON body. Client integrations should key on the HTTP status code, not string-match the message text.
401 — Missing or invalid API key
{"detail": "Missing token"}
{"detail": "Invalid API key"}403 — Plan doesn't include API access
{"detail": {"error": "API access requires the Pro plan or above."}}422 — Validation error
{"detail": {"error": "target_url must be an https:// URL."}}
{"detail": {"error": "Unsupported event 'thing'. Allowed: ['new_lead']."}}429 — Rate limit exceeded
{"detail": "Rate limit exceeded"}Back off and retry after the interval implied by Retry-After if present.
07Rate limits
120 requests per minute per API key. Applies to every /api/v1/* endpoint. If you need more, drop us a note at hello@bioleads.link — we'll bump you.
Rate limits do not apply to inbound webhook deliveries from us to your endpoint — those are gated by the per-lead retry policy above.