Leads
Esta página aún no está disponible en tu idioma.
Leads are inbound prospects captured before they become customers, from the BenchKey lead widget, live chat, manual entry, or an import. A lead carries the contact information the prospect supplied, a workflow status (e.g. new, won, lost), a source, optional UTM attribution, and, once worked, links to the converted ticket and/or a matched customer record.
Archived leads are hidden from list responses by default. Pass include_archived=true to include them.
The lead object
Section titled “The lead object”{ "object": "lead", "id": "4821", "status": "new", "source": "lead_widget", "channel": "widget", "type": null, "priority": null, "urgency_tag": null, "lead_category": null, "lead_path": null, "category_id": null, "customer_name": "Alex Rivera", "customer_email": "alex.rivera@example.com", "customer_phone": "512-555-0142", "contact_name": null, "contact_email": null, "contact_phone": null, "assigned_to": null, "ai_summary": "Customer has a cracked screen on an iPhone 15 Pro.", "quoted_price": { "amount_cents": 14900, "amount": "149.00", "currency": "usd" }, "source_page_url": "https://yourshop.com/repair", "source_referrer": null, "utm_source": "google", "utm_medium": "cpc", "utm_campaign": "iphone-repair", "utm_term": null, "utm_content": null, "first_response_at": null, "linked_ticket_id": null, "merged_into_lead_id": null, "duplicate_of_lead_id": null, "matched_customer_id": null, "matched_ticket_customer_id": null, "customer": null, "converted_ticket_id": null, "converted_at": null, "last_activity_at": "2025-12-01T18:44:00.000Z", "archived_at": null, "created_at": "2025-12-01T18:44:00.000Z", "updated_at": "2025-12-01T18:44:00.000Z"}| Field | Type | Description |
|---|---|---|
id | string | Lead id |
status | string|null | Workflow status: new, contacted, quoted, won, lost, etc. Discover the tenant’s live status registry with GET /statuses/leads |
source | string|null | Capture source (e.g. lead_widget, manual) |
channel | string|null | Inbound channel: email, sms, phone, voicemail, widget, or manual |
type | string|null | Lead type (read-only, set by the app) |
priority | string|null | Priority level (read-only) |
urgency_tag | string|null | Urgency label set by staff |
lead_category | string|null | Lead category label |
lead_path | string|null | Lead path/flow identifier, local or mail_in |
category_id | string|null | Repair category id associated with the lead, represented as a string |
customer_name | string|null | Prospect’s name |
customer_email | string|null | Prospect’s email |
customer_phone | string|null | Prospect’s phone |
contact_name | string|null | Secondary contact name |
contact_email | string|null | Secondary contact email |
contact_phone | string|null | Secondary contact phone |
assigned_to | string|null | Staff member assigned to this lead |
ai_summary | string|null | AI-generated summary of the lead |
quoted_price | object|null | Quoted price, { amount_cents, amount, currency } |
source_page_url | string|null | URL the lead was captured on |
source_referrer | string|null | HTTP Referer at capture time |
utm_source | string|null | UTM source |
utm_medium | string|null | UTM medium |
utm_campaign | string|null | UTM campaign |
utm_term | string|null | UTM term |
utm_content | string|null | UTM content |
first_response_at | string|null | ISO-8601 timestamp of the shop’s first response, the response-time SLA signal for reporting tools |
linked_ticket_id | string|null | Active ticket this lead’s inbox is locked to (distinct from converted_ticket_id) |
merged_into_lead_id | string|null | The surviving lead this one was merged into. A merged lead reads status dead, use this pointer to reconcile instead of double-counting |
duplicate_of_lead_id | string|null | The original lead this one was marked a duplicate of |
matched_customer_id | string|null | Public customer email id this lead is linked to (dereferenceable via GET /customers/:id) |
matched_ticket_customer_id | string|null | BenchKey’s internal all-tickets order_id customer-link value |
customer | object|null | Summary of the linked customer, { object, id, name, email, phone }, or null if unlinked |
converted_ticket_id | string|null | Ticket id created when the lead was converted |
converted_at | string|null | ISO-8601 timestamp of conversion |
last_activity_at | string|null | ISO-8601 timestamp of last activity |
archived_at | string|null | ISO-8601 timestamp of archival, or null if active |
created_at | string | ISO-8601 creation timestamp |
updated_at | string | ISO-8601 last-updated timestamp |
List leads
Section titled “List leads”GET /api/v1/leadsReturns a cursor-paginated list of leads, newest first. Archived leads are excluded unless include_archived=true.
Scope required: leads.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
status | string | Filter by workflow status (e.g. new, won, lost) |
source | string | Filter by capture source |
channel | string | Filter by inbound channel |
customer_id | string | Filter to leads matched to a specific customer. Accepts the customer’s email address (the public customer id) or a positive integer order-id. Blank or whitespace-only values return 400 invalid_field. |
converted | boolean | true = only converted leads; false = only unconverted. Any other value returns 400 invalid_filter. |
q | string | Search by customer/contact name, email, or phone |
updated_since | string | ISO-8601 or Unix epoch, only leads updated at or after this time. Arrays or objects return 400 invalid_query; non-parseable strings return 400 invalid_filter |
include_archived | boolean | Include archived leads (default: false). Accepted values: true/false/1/0/yes/no. Any other value returns 400 invalid_filter |
limit | integer | Page size, 1–100 (default: 25) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/leads?status=new&limit=10" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/leads?status=new&limit=10", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "lead", "id": "4821", "status": "new", "source": "lead_widget", "channel": "widget", "customer_name": "Alex Rivera", "customer_email": "alex.rivera@example.com", "customer_phone": "512-555-0142", "quoted_price": { "amount_cents": 14900, "amount": "149.00", "currency": "usd" }, "matched_customer_id": null, "customer": null, "converted_ticket_id": null, "converted_at": null, "archived_at": null, "created_at": "2025-12-01T18:44:00.000Z", "updated_at": "2025-12-01T18:44:00.000Z" } ], "has_more": false, "next_cursor": null}See Pagination for how to page through results.
Retrieve a lead
Section titled “Retrieve a lead”GET /api/v1/leads/:idFetches a single lead by id.
Scope required: leads.read
curl "https://app.benchkey.com/api/v1/leads/4821" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },});const lead = await res.json();Response
Section titled “Response”Returns the lead object. Returns 404 if no lead with that id exists.
Archived leads return 404 by default.
GET /:idapplies the same visibility filter as the list: archived leads are hidden unless you pass?include_archived=true. This keeps the single-record path consistent with what the list exposes.
Create a lead
Section titled “Create a lead”POST /api/v1/leadsCreates an inbound lead. At least one of customer_name, customer_email, or customer_phone is required. Fires the same follow-up-sequence enrollment, auto-assignment, and customer auto-match the app performs for a staff-created lead.
Scope required: leads.write
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
customer_name | one of three* | Prospect’s name |
customer_email | one of three* | Prospect’s email address |
customer_phone | one of three* | Prospect’s phone number |
channel | no | email, sms, phone, voicemail, widget, or manual (default: manual) |
category_id | no | Repair category id (positive integer; non-integer or non-positive values return 400 invalid_field) |
assigned_to | no | Staff user id to assign the lead to |
lead_category | no | Lead category label |
lead_path | no | Lead path/flow identifier |
notes | no | Internal note recorded on the lead |
* At least one of customer_name, customer_email, or customer_phone must be provided.
curl -X POST https://app.benchkey.com/api/v1/leads \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "customer_name": "Alex Rivera", "customer_email": "alex.rivera@example.com", "customer_phone": "512-555-0142", "channel": "widget", "notes": "Cracked screen, iPhone 15 Pro" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ customer_name: "Alex Rivera", customer_email: "alex.rivera@example.com", customer_phone: "512-555-0142", channel: "widget", notes: "Cracked screen, iPhone 15 Pro", }),});const lead = await res.json(); // HTTP 201Response
Section titled “Response”Returns the created lead object with HTTP 201.
Update a lead
Section titled “Update a lead”PATCH /api/v1/leads/:idUpdates one or more mutable fields on a lead. A status change drives the follow-up-sequence lifecycle (enrollment or cancellation) just as a staff edit would. Only fields included in the request body are changed.
Scope required: leads.write
Request body
Section titled “Request body”At least one field must be provided:
| Field | Description |
|---|---|
status | New workflow status (e.g. contacted, quoted, won, lost) |
assigned_to | Staff user id, or null to unassign |
category_id | Repair category id |
customer_name | Prospect’s name |
customer_email | Prospect’s email |
customer_phone | Prospect’s phone |
contact_name | Secondary contact name |
contact_email | Secondary contact email |
contact_phone | Secondary contact phone |
on_behalf_of | Name of person on whose behalf the contact is calling |
urgency_tag | Urgency label |
quoted_price | Quoted price in dollars (e.g. 149.00), or null to clear |
lead_category | Lead category label |
lead_path | Lead path/flow identifier |
curl -X PATCH "https://app.benchkey.com/api/v1/leads/4821" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "status": "quoted", "quoted_price": 149.00 }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821", { method: "PATCH", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ status: "quoted", quoted_price: 149.00 }),});const lead = await res.json();Response
Section titled “Response”Returns the updated lead object.
Archived leads return 404 on PATCH.
PATCH /:idapplies the same visibility rule as the list andGET /:id: an archived lead is treated as non-existent and returns404 not_found. To update an archived lead, first restore it viaPOST /:id/unarchive.
Archive a lead
Section titled “Archive a lead”POST /api/v1/leads/:id/archiveMoves a lead to the archived bucket. Archived leads are excluded from list responses unless include_archived=true.
Scope required: leads.write
curl -X POST "https://app.benchkey.com/api/v1/leads/4821/archive" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821/archive", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },});const lead = await res.json();Response
Section titled “Response”Returns the updated lead object with archived_at set.
Unarchive a lead
Section titled “Unarchive a lead”POST /api/v1/leads/:id/unarchiveRestores an archived lead to active status.
Scope required: leads.write
curl -X POST "https://app.benchkey.com/api/v1/leads/4821/unarchive" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821/unarchive", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },});const lead = await res.json();Response
Section titled “Response”Returns the updated lead object with archived_at set to null.
Convert a lead to a ticket
Section titled “Convert a lead to a ticket”POST /api/v1/leads/:id/convert-to-ticketConverts an inbound lead into a work ticket. Fires the full conversion workflow, order numbering, audit log, queue routing, and any kiosk/mail-in side effects. The lead is atomically claimed so a repeated call cannot create a second ticket.
Returns the updated lead, whose converted_ticket_id is the newly created ticket’s id.
Scope required: leads.write
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
service_name | yes | Description of the work/issue |
customer_name | no | Overrides the lead’s name on the ticket (defaults to lead’s name) |
customer_email | no | Customer email on the ticket |
customer_phone | no | Customer phone on the ticket |
device_name | no | Device description (e.g. “iPhone 15 Pro”) |
category_id | no | Repair category id |
task_type | no | 1 = mail-in, 2 = walk-in (default) |
assigned_to | no | Staff user id |
queue_name | no | Queue to route the ticket to |
price | no | Starting price on the ticket |
notes | no | Internal notes to copy to the ticket |
address1 | no | Street address (for mail-in) |
address2 | no | Address line 2 |
city | no | City |
state | no | State |
zip | no | Postal code |
trigger_kiosk_signature | no | true to trigger the kiosk signature flow after conversion (boolean) |
carry_over_attachments | no | true to copy the lead’s file attachments to the new ticket (boolean) |
send_mail_in_link | no | true to send a mail-in shipping label link to the customer (boolean) |
curl -X POST "https://app.benchkey.com/api/v1/leads/4821/convert-to-ticket" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "service_name": "Cracked screen replacement", "device_name": "iPhone 15 Pro", "price": "149.00" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821/convert-to-ticket", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ service_name: "Cracked screen replacement", device_name: "iPhone 15 Pro", price: "149.00", }),});const lead = await res.json();// lead.converted_ticket_id is the new ticket idResponse
Section titled “Response”Returns the updated lead object. converted_ticket_id is set to the new ticket’s id.
Link a lead to a customer
Section titled “Link a lead to a customer”POST /api/v1/leads/:id/link-customerAttaches the lead to an existing customer record. The internal matcher resolves customer_id by numeric id, email address, or phone number. Returns the updated lead with customer populated.
Scope required: leads.write
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
customer_id | yes | Customer id, email, or phone to link |
curl -X POST "https://app.benchkey.com/api/v1/leads/4821/link-customer" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "alex.rivera@example.com" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821/link-customer", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ customer_id: "alex.rivera@example.com" }),});const lead = await res.json();// lead.customer is now populatedResponse
Section titled “Response”Returns the updated lead object with customer populated.
Unlink a lead’s customer
Section titled “Unlink a lead’s customer”DELETE /api/v1/leads/:id/link-customerClears the lead’s customer match, useful to undo a wrong link or treat the lead as brand-new. Returns the updated lead with customer and matched_customer_id cleared.
Scope required: leads.write
curl -X DELETE "https://app.benchkey.com/api/v1/leads/4821/link-customer" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/leads/4821/link-customer", { method: "DELETE", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },});const lead = await res.json();Response
Section titled “Response”Returns the updated lead object with customer set to null.
Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_id | The :id is not a valid integer |
400 | invalid_query | A list filter parameter was supplied as an array or object instead of a single string value |
400 | missing_contact | POST body has none of customer_name, customer_email, or customer_phone |
400 | missing_field | A required field is absent (e.g. service_name for convert) |
400 | invalid_field | A field value is invalid (e.g. malformed email, negative quoted_price, non-integer customer_id filter, or non-positive-integer category_id on create/update/convert) |
400 | no_updatable_fields | PATCH body contains no recognized fields |
400 | invalid_filter | A filter value is invalid: updated_since is not a valid timestamp, converted is not a recognized boolean, or include_archived is not a recognized boolean (true/false/1/0/yes/no) |
404 | not_found | No lead with that id exists |
409 | conflict | Lead is already converted (cannot convert twice) |
403 | insufficient_scope | API key lacks leads.read or leads.write |
See Errors for the full error envelope format.