Skip to main content

Managing Leads

Once a lead exists, you can keep it in sync with your own system:

Both endpoints, and every other /v1/leads/{leadID}/... endpoint, accept either kind of lead identifier described below.

To read leads back, including changes made in ABC Sales AI, see Reading and Polling Leads.

Identifying a lead​

{leadID} in the path can be either:

  • The ABC Sales AI lead ID, a positive whole number such as 1234567.
  • Your external ID, the id you sent to POST /v1/webhooks/leads. If you did not send one, use the id returned in that call's 202 response: we generate one for you.

That means you can use a lead straight after creating it, with no lookup:

# 1. Create the lead. The 202 response carries data.id.
curl -X POST "https://api.abcsalesbot.com/v1/webhooks/leads" \
-H "Authorization: Bearer $ABC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Jane Doe", "phone": "+6591234567", "id": "student-48213"}'
# {"data": {"id": "student-48213", "status": "accepted"}}

# 2. Use that id directly.
curl -X PUT "https://api.abcsalesbot.com/v1/leads/student-48213/ai-handling" \
-H "Authorization: Bearer $ABC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"handled_by_ai": false}'
Send your own id

A lead keeps one external ID: the one from the latest POST /v1/webhooks/leads call that reached it. If a later call matches the same lead by phone or another channel identifier, the lead's external ID is replaced by that call's id, or by a newly generated one when you omit id. The earlier ID then stops resolving (404). Always sending your own stable ID for a person avoids this.

How the identifier is resolved​

Lookups only ever see leads in your own company.

{leadID}What we look upResult
Not a whole number (e.g. student-48213, a UUID)Your external IDThe matching lead
A whole number (e.g. 48213)Both our lead ID and your external IDThe matching lead, if only one of them matches (or both match the same lead)
Anything, with ?id_type=lead_idOur lead ID onlyThe matching lead
Anything, with ?id_type=external_idYour external ID onlyThe matching lead

You only need id_type to settle an ambiguous match (below). If your external IDs are numeric, sending ?id_type=external_id on every call is a safe habit.

Not found yet: retry after creating a lead​

Lead intake is asynchronous. POST /v1/webhooks/leads returns 202 once the lead is queued, and the lead is created a moment later. A follow-up call made immediately can therefore return 404 Not Found for a few seconds. Retry with a short backoff (for example 1, 2, then 4 seconds). A 404 that persists means no lead in your company has that identifier.

Ambiguous identifiers​

We never guess which lead you meant. You get 409 Conflict with "error_code": "lead_ambiguous" when:

  • a numeric {leadID} is the lead ID of one lead and the external ID of a different one. Repeat the call with ?id_type=lead_id or ?id_type=external_id to say which you mean.
  • more than one of your leads has the same external ID. id_type cannot settle this one: use the lead's numeric ID instead.
{"error": "This identifier matches more than one lead", "error_code": "lead_ambiguous"}

To avoid duplicates in the first place, send your own stable, unique ID as id when you create each lead (for example your CRM or student record ID): resending the same id updates the existing lead instead of creating a new one.

Remove tags from a lead​

DELETE /v1/leads/{leadID}/tags
curl -X DELETE "https://api.abcsalesbot.com/v1/leads/student-48213/tags" \
-H "Authorization: Bearer $ABC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tags": ["trial", "follow-up"]}'

The JSON body is required. tags is a list of 1 to 50 tag names, matched exactly (case-sensitive) against your company's tags, which you can list with GET /v1/tags. Names are trimmed, and duplicates in the list are ignored.

The call is idempotent. A tag the lead does not have, or one that does not exist in your account at all, is reported in not_attached rather than failing the request, so it is safe to repeat:

{
"data": {
"lead_id": 1234567,
"external_id": "student-48213",
"removed": ["trial"],
"not_attached": ["follow-up"],
"tags": ["paid"]
}
}

tags is the lead's full tag list after the removal. Removing a tag from a lead never deletes the tag from your account, and other leads keep it. To add tags, send them in tags on POST /v1/webhooks/leads.

When at least one tag is removed, the change appears in the ABC Sales AI inbox straight away and is delivered to your webhook subscriptions as a lead update.

Avoid update loops

If your system listens for lead updates through a webhook subscription and reacts by calling this endpoint, make sure it does not act again on the update its own call caused.

Turn AI replies on or off​

PUT /v1/leads/{leadID}/ai-handling
curl -X PUT "https://api.abcsalesbot.com/v1/leads/student-48213/ai-handling" \
-H "Authorization: Bearer $ABC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"handled_by_ai": false}'

handled_by_ai is required and must be a JSON boolean: "false" as a string is rejected with 400. The response reports the lead's resulting setting:

{
"data": {
"lead_id": 1234567,
"external_id": "student-48213",
"handled_by_ai": false
}
}
  • false: the AI stops replying to this lead, and your team handles the conversation in the inbox.
  • true: the AI may reply to this lead again.

Either value replaces a timed pause set from the inbox: false turns AI off with no end date, and true turns it back on now.

A change is delivered to your webhook subscriptions as a lead update. Sending the value the lead already has returns 200 and sends no webhook.

Three switches control AI replies

The AI replies to a lead only when AI is enabled for your company, for the messaging channel the lead is on, and for the lead. This endpoint controls the lead-level switch only. Setting true does not turn on AI for a company or channel where it is off.

To create a lead with AI already off, send "handled_by_ai": false on POST /v1/webhooks/leads. That field only applies when the call creates a new lead; to change an existing lead, use this endpoint.

Rate limits and errors​

These two endpoints share a rate limit of 600 requests per 10 minutes, see Rate Limits. For the error format, see Errors.

StatusWhen
400Invalid body (missing or empty tags, more than 50 tags, handled_by_ai missing or not a boolean), or an invalid id_type
404No lead in your company has that identifier (or it is still being created, see above)
409The identifier matches more than one lead (error_code: lead_ambiguous), see Ambiguous identifiers
429Rate limit exceeded