TCPA responsibility
Every lead ingested through this API must have documented prior express written consent from the consumer to receive autodialed marketing calls and texts. Best practice — and your strongest defense in a TCPA complaint — is a disclosure that names the receiving advisor (or their firm) as the recipient, retained verbatim with timestamp and IP. The endpoint rejects any request where consent !== true. Statutory damages for a TCPA violation are $500–$1,500 per text or call. AnchorLeads does not indemnify agents for non-compliant imports.
Endpoint
POST /api/ingest/leads Host: anchorleads.io Authorization: Bearer al_live_YOUR_KEY_HERE Content-Type: application/jsonGenerate a key in Agent settings → API keys. The plaintext key is shown exactly once at creation — store it in your integration's secrets manager. We only keep a SHA-256 hash.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| first_name | string | required | Prospect first name. 1–80 chars. |
| last_name | string | required | Prospect last name. 1–80 chars. |
| string | required | Valid email. Lowercased server-side. | |
| phone | string | required | US phone. 10 digits or E.164. Normalized server-side. |
| consent | boolean (must be true) | required | Consent to be contacted. Rejected if not exactly true. |
| consent_timestamp | ISO 8601 datetime | optional | When the prospect gave consent. Defaults to now. |
| consent_disclosure | string ≤ 4000 | optional | Verbatim disclosure text the prospect agreed to. Retained for audit. |
| consent_url | URL string ≤ 500 | optional | The page where consent was captured. Retained for audit. |
| consent_ip | string ≤ 64 | optional | The prospect's IP at consent time. Retained for audit. |
| source | string ≤ 80 | optional | Free-form source label. Stored on the lead as lead_source (e.g., "everquote", "custom_form"). |
| funnel_type | "coverage_gap" | "iul_tax_savings" | optional | Product interest. Defaults to "coverage_gap". |
| lead_type | "iul" | "term" | optional | Product classification, if known. |
| metadata | JSON object | optional | Arbitrary tags stored on the lead's created event. |
Examples
curl
curl -X POST https://anchorleads.io/api/ingest/leads \
-H "Authorization: Bearer al_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Michael",
"last_name": "Reyes",
"email": "michael@example.com",
"phone": "+15551234501",
"consent": true,
"consent_timestamp": "2026-07-07T15:04:00Z",
"consent_disclosure": "By checking this box I agree to receive marketing calls and text messages from Michael Advisor at the number I provided...",
"consent_url": "https://youracquisitionsite.com/quote-form",
"consent_ip": "184.28.14.9",
"source": "everquote",
"funnel_type": "iul_tax_savings",
"lead_type": "iul",
"metadata": { "campaign": "fb-tax-drag-2026-07", "cost_per_lead": 27.50 }
}'Node.js
const res = await fetch('https://anchorleads.io/api/ingest/leads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ANCHORLEADS_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
first_name: lead.firstName,
last_name: lead.lastName,
email: lead.email,
phone: lead.phone,
consent: true,
consent_timestamp: lead.consentAt,
consent_disclosure: lead.disclosureText,
consent_url: lead.consentPageUrl,
consent_ip: lead.consentIp,
source: 'custom_form',
}),
});
const json = await res.json();
if (!res.ok || !json.success) {
console.error('Ingest failed:', json);
throw new Error(json.message ?? 'Ingest failed');
}
console.log('Lead created:', json.data.lead_id, 'deduped:', json.data.deduped);Python
import os, requests
payload = {
"first_name": lead["first_name"],
"last_name": lead["last_name"],
"email": lead["email"],
"phone": lead["phone"],
"consent": True,
"consent_timestamp": lead["consent_at"],
"consent_disclosure": lead["disclosure"],
"consent_url": lead["consent_page_url"],
"source": "custom_form",
}
resp = requests.post(
"https://anchorleads.io/api/ingest/leads",
headers={
"Authorization": f"Bearer {os.environ['ANCHORLEADS_KEY']}",
"Content-Type": "application/json",
},
json=payload,
timeout=10,
)
resp.raise_for_status()
data = resp.json()["data"]
print("Lead created:", data["lead_id"], "deduped:", data["deduped"])Responses
201 Created
{ "success": true, "data": { "lead_id": "uuid", "deduped": false, "do_not_contact": false } }New lead accepted and saved.
200 OK (deduped)
{ "success": true, "data": { "lead_id": "uuid", "deduped": true } }Same phone or email already exists in this agent's book within the last 30 days. Existing lead is returned.
400 Bad Request
{ "success": false, "error": "validation_error", "field": "consent", "message": "consent must be exactly true..." }Schema validation failed or consent was missing / not exactly true.
401 Unauthorized
{ "success": false, "error": "missing_token" | "invalid_token" | "unknown_key" | "revoked" }Authorization header missing, malformed, or does not resolve to an active key.
403 Forbidden
{ "success": false, "error": "tcpa_ack_required" | "expired" | "scope_denied" }Key was created but the TCPA clause was never accepted, is expired, or lacks the leads:write scope.
429 Too Many Requests
{ "success": false, "error": "rate_limited" }
Header: Retry-After: <seconds>Daily rate limit reached. Retry after the given delay.
Behavior
Dedupe
If the same phone or email already exists in this agent's book within the last 30 days, we return the existing lead ID with deduped: true. No duplicate row is created.
DNC suppression
Every ingested phone is checked against prior STOP records for the agent. Matches are still saved but with do_not_contact = true, and all outbound automations skip them.
Consent audit trail
consent_disclosure, consent_url, consent_ip, and consent_timestamp are stored verbatim on the lead. Retain the original consent evidence on your side too — this is your defense record if a TCPA claim is filed.
Rate limits
Each key has a per-day cap (default 500). Exceeding it returns 429 with a Retry-After header pointing to midnight UTC. Adjust the cap when creating the key.
No CORS
This is a server-to-server API. Browser requests are not allowed. Do not put your API key in client-side code.