Skip to main content

Leads and Contacts

Manage the tenant's unified contact list: add a single contact with consent, search across leads plus their recent call and SMS history, bulk-import into a campaign list, and run per-lead CRUD. Phone numbers are normalized to E.164 on every write.

Add a single contact

POST /api/v1/contacts adds a single contact (lead) with tenant-wide phone dedup. Scope: write_crm.

Request body
{
"first_name": "Jane",
"last_name": "Doe",
"phone": "+15551234567",
"email": "jane@example.com",
"company_name": "Acme LLC",
"consent_acknowledged": true,
"source": "website-form",
"tags": ["vip", "warm-lead"]
}
  • first_name and phone are required (E.164 preferred; 10-digit US numbers are auto-normalized).
  • consent_acknowledged: true is REQUIRED. Omitting it or setting it to false returns 422. The audit trail is written to custom_fields.consent for TCPA traceability.
  • source defaults to api-manual-add; tags are appended to defaults.

200 for both new and duplicate: branch on already_existed

Both outcomes return 200 OK. Branch on already_existed, not the status code. If the phone already exists anywhere in the tenant's leads, the existing lead_id is returned instead of creating a duplicate.

Response (200 OK, new)
{
"lead_id": "uuid",
"phone": "+15551234567",
"already_existed": false,
"first_name": "Jane",
"last_name": "Doe",
"consent_captured_at": "2026-04-19T15:00:00Z"
}
Response (200 OK, duplicate returns the existing row)
{
"lead_id": "uuid",
"phone": "+15551234567",
"already_existed": true,
"first_name": "Jane",
"last_name": "Doe",
"consent_captured_at": null
}
TCPA compliance

Setting consent_acknowledged: true without actual prior consent from the contact is a compliance violation. The API trusts the caller but logs captured_at and captured_by for audit.

Search leads (with history)

GET /api/v1/leads/search searches leads by name, company, or phone and returns recent call and SMS history per match. Scope: read.

ParameterDescription
queryFree-text match against first_name, last_name, company_name, and (if 4+ digits) phone suffix
phoneExact phone match (normalized to E.164 before lookup); use for precise caller lookups
limitMax leads returned (1-50, default 10)
history_limitMax call plus SMS entries per lead (1-20, default 5)

At least one of query or phone must be provided. Results are sorted by last_contacted_at (descending, nulls last). Customer-submitted strings (customer_name, SMS body) are wrapped in <tool_data>...</tool_data> tags so downstream AI consumers treat them as data, not instructions.

Example: exact phone lookup
curl "https://app.autorev.ai/api/v1/leads/search?phone=%2B15551234567&history_limit=3" \
-H "Authorization: Bearer ar_live_..."

Each matched lead includes recent_calls (with outcome, duration_sec, timestamps) and recent_sms (with direction, body, status), plus lead fields like status, source, lead_score, first_seen_at, and last_contact_at. The response echoes query_used.

Bulk import (list-scoped)

POST /api/v1/leads/bulk upserts up to 1000 leads in a single call. Scope: write_crm. It auto-creates a lead list, so the returned list_id can target a subsequent POST /api/v1/campaigns.

List-scoped dedup, not tenant-wide

Duplicates within a list (same tenant + list + phone) are silently skipped and reported under skipped. The same phone CAN appear in multiple lists. If you want one row per phone across the whole tenant, use POST /api/v1/contacts instead.

Request body
{
"leads": [
{
"phone": "+15551234567",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"address": "123 Main St",
"city": "Orlando",
"state": "FL",
"zip": "32801",
"source": "spring-promo-2026",
"tags": ["vip", "warranty"],
"notes": "Prefers morning appointments",
"chatbot_enabled": false,
"company_name": "Acme LLC",
"external_id": "crm_7890"
}
],
"list_name": "Spring Promo",
"list_description": "From our CRM",
"tag": "spring-promo-2026",
"source": "api-bulk-import"
}

leads is required (1-1000 items); within each lead only phone is required (any format, normalized). list_name defaults to API Import YYYY-MM-DD, source defaults to api-bulk-import, tag is applied to every lead, and chatbot_enabled adds a chatbot-enabled tag.

Response
{
"list_id": "a1b2c3d4-...",
"list_name": "Spring Promo",
"inserted": 485,
"skipped": [
{ "phone": "+15551234567", "reason": "duplicate_within_batch" },
{ "phone": "abc", "reason": "invalid_phone_format" }
],
"lead_ids": ["uuid-1", "uuid-2", "..."],
"total_submitted": 500
}

Skip reasons: invalid_phone_format, duplicate_within_batch, duplicate_in_list, insert_error.

Leads CRUD and tags

List leads

GET /api/v1/leads returns a paginated lead list with simple filters: phone, email, tag, list_id, status.

Example
curl -H "Authorization: Bearer ${AUTOREV_API_KEY}" \
"https://app.autorev.ai/api/v1/leads?tag=spring-promo&limit=25"
Response (200)
{
"data": [
{
"id": "4b3e...",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+15551234567",
"email": "jane@example.com",
"status": "ready",
"tags": ["spring-promo", "chatbot-enabled"],
"created_at": "2026-04-18T14:02:11Z"
}
],
"has_more": true,
"cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0xOFQxMzoyMDowMFoiLCJpZCI6IjRiM2UuLi4ifQ=="
}

Get a lead

GET /api/v1/leads/:id returns lead detail with the last 10 SMS messages inline:

Response (200)
{
"lead": { "id": "4b3e...", "phone": "+15551234567", "tags": ["spring-promo"] },
"recent_messages": [
{ "id": "...", "direction": "inbound", "body": "yes interested", "created_at": "2026-04-19T10:12:00Z" }
]
}

Update a lead

PATCH /api/v1/leads/:id performs a partial update. custom_fields is merged: send null for a key to delete it.

Example
curl -X PATCH -H "Authorization: Bearer ${AUTOREV_API_KEY}" \
-H "Content-Type: application/json" \
https://app.autorev.ai/api/v1/leads/4b3e... \
-d '{
"tags": ["spring-promo", "replied"],
"custom_fields": { "last_contacted_via": "sms", "notes": "Called back, wants quote" },
"chatbot_enabled": true
}'

Tag operations

POST /api/v1/leads/:id/tags atomically adds and/or removes tags. At least one of add/remove must be non-empty.

Example
curl -X POST -H "Authorization: Bearer ${AUTOREV_API_KEY}" \
-H "Content-Type: application/json" \
https://app.autorev.ai/api/v1/leads/4b3e.../tags \
-d '{ "add": ["vip"], "remove": ["cold"] }'

Delete a lead (soft delete)

DELETE /api/v1/leads/:id soft-deletes: it sets status=opted_out plus opted_out_at. The row is preserved so TCPA records persist.

Response (200)
{ "id": "4b3e...", "deleted": true, "status": "opted_out" }

Lead Lists

Lead lists group leads for campaign targeting. A lead can appear in multiple lists: "attaching" a lead to a new list clones the row with a new list_id so the source row stays intact.

Create a list

POST /api/v1/lead-lists:

Example
curl -X POST -H "Authorization: Bearer ${AUTOREV_API_KEY}" \
-H "Content-Type: application/json" \
https://app.autorev.ai/api/v1/lead-lists \
-d '{ "name": "Spring Promo 2026", "description": "Existing customers, wireless phones only" }'
Response (201)
{
"list": {
"id": "d2c1...",
"name": "Spring Promo 2026",
"total_leads": 0,
"usable_leads": 0,
"created_at": "2026-04-22T13:00:00Z"
}
}

List and get

  • GET /api/v1/lead-lists returns a paginated list of all lead lists in the tenant.
  • GET /api/v1/lead-lists/:id returns list detail with a live lead count (not the denormalized counter):
Response (200)
{
"list": { "id": "d2c1...", "name": "Spring Promo 2026", "total_leads": 412 },
"lead_count": 412
}

Attach leads to a list

POST /api/v1/lead-lists/:id/leads attaches existing leads (by id) to the list. In-list duplicates are skipped silently.

Example
curl -X POST -H "Authorization: Bearer ${AUTOREV_API_KEY}" \
-H "Content-Type: application/json" \
https://app.autorev.ai/api/v1/lead-lists/d2c1.../leads \
-d '{ "lead_ids": ["4b3e...", "7fa2..."] }'
Response (200)
{
"list_id": "d2c1...",
"attached": 2,
"new_lead_ids": ["9aa1...", "a4d2..."],
"skipped": [],
"total_submitted": 2
}

Remove and delete

  • DELETE /api/v1/lead-lists/:id/leads/:leadId removes one lead row from a list. Surgical: it does not touch copies of the same phone in other lists.
  • DELETE /api/v1/lead-lists/:id deletes a lead list and cascades to all attached lead rows.