Managing Leads
Once a lead exists, you can keep it in sync with your own system:
- Remove tags when the lead's status changes on your side.
- Turn AI replies on or off for that one lead, to hand it between the AI and your team.
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
idyou sent toPOST /v1/webhooks/leads. If you did not send one, use theidreturned in that call's202response: 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}'
idA 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 up | Result |
|---|---|---|
Not a whole number (e.g. student-48213, a UUID) | Your external ID | The matching lead |
A whole number (e.g. 48213) | Both our lead ID and your external ID | The matching lead, if only one of them matches (or both match the same lead) |
Anything, with ?id_type=lead_id | Our lead ID only | The matching lead |
Anything, with ?id_type=external_id | Your external ID only | The 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_idor?id_type=external_idto say which you mean. - more than one of your leads has the same external ID.
id_typecannot 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.
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.
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.
| Status | When |
|---|---|
400 | Invalid body (missing or empty tags, more than 50 tags, handled_by_ai missing or not a boolean), or an invalid id_type |
404 | No lead in your company has that identifier (or it is still being created, see above) |
409 | The identifier matches more than one lead (error_code: lead_ambiguous), see Ambiguous identifiers |
429 | Rate limit exceeded |