Tickets
Este conteúdo não está disponível em sua língua ainda.
Tickets are the core work-order record in BenchKey, each ticket represents one repair or service job. A ticket has a stable id (the order_id string), customer contact info, an optional device, a status that moves through your workflow, and a running payment total.
The ticket object
Section titled “The ticket object”{ "object": "ticket", "id": "TK-10042", "internal_id": "10042", "status": "Waiting for Parts", "priority": null, "device": "iPhone 15 Pro", "ticket_type": null, "is_mailin": false, "location_id": "1", "assigned_to": "alex@repairshop.com", "queue_name": null, "source": "walk-in", "how_did_you_find_us": null, "address": { "line1": null, "line2": null, "city": null, "state": null, "postal_code": null }, "customer": { "object": "ticket_customer", "name": "Jane Smith", "email": "jane.smith@example.com", "phone": "555-867-5309" }, "custom_fields": {}, "is_business": false, "business_company": null, "business_reference": null, "terms_accepted_at": null, "amount_paid": { "amount_cents": 8999, "amount": "89.99", "currency": "USD" }, "lead_id": null, "category_id": null, "devices": [ { "object": "ticket_device", "id": "7291", "asset_id": null, "status": "In Repair", "issue_reported": "Cracked screen", "work_performed": "Replaced display assembly" } ], "due_at": null, "closed_at": null, "created_at": "2025-11-01T10:14:22.000Z", "updated_at": "2025-11-03T16:45:00.000Z"}| Field | Type | Description |
|---|---|---|
id | string | Canonical order ID (stable across the ticket’s lifetime) |
internal_id | string|null | Internal numeric ID, if your shop uses it |
status | string|null | Current workflow status (tenant-configurable) |
priority | string|null | Priority label, or null |
device | string|null | Device description |
ticket_type | string|null | Ticket type label |
is_mailin | boolean | true for mail-in / depot jobs |
location_id | string|null | Location this ticket belongs to |
assigned_to | string|null | Tech email assigned to the ticket |
queue_name | string|null | Name of the queue the ticket is currently in |
source | string|null | How the ticket arrived (walk-in, web, etc.) |
how_did_you_find_us | string|null | Referral source recorded at intake |
address | object | Customer mailing address fields (line1, line2, city, state, postal_code) |
customer | object | Denormalized customer contact summary |
custom_fields | object | Tenant-defined New Ticket custom fields (key/value pairs; values are strings, numbers, booleans, or null) |
is_business | boolean | true for a B2B intake ticket (mail-in business gate) |
business_company | string|null | Company name recorded on a business intake |
business_reference | string|null | Customer’s own reference / PO number recorded on a business intake |
terms_accepted_at | string|null | ISO-8601 timestamp the customer accepted the shop’s terms, or null |
amount_paid | object|null | Sum of all payments recorded against the ticket, both payments taken in BenchKey and imported legacy payments |
lead_id | string|null | ID of the lead this ticket was converted from, if any |
category_id | string|null | Check-in category ID associated with the ticket, if any |
deleted | boolean | Present (true) only on soft-deleted rows returned under include_deleted=true; visible tickets omit the field |
deleted_at | string|null | ISO-8601 soft-delete timestamp (present only alongside deleted) |
hidden | boolean | Present (true) only on hidden rows returned under include_hidden=true; visible tickets omit the field |
devices | array | Attached devices/assets (present on single-ticket reads) |
due_at | string|null | ISO-8601 due date |
closed_at | string|null | ISO-8601 timestamp the ticket was closed |
created_at | string | ISO-8601 creation timestamp |
updated_at | string | ISO-8601 last-updated timestamp |
The devices array is included on GET /tickets/:id responses. List responses omit it for performance.
To discover the status strings your workflow accepts, use the Statuses discovery endpoint, it returns the tenant’s live status catalog (and queues).
List tickets
Section titled “List tickets”GET /api/v1/ticketsReturns a cursor-paginated list of tickets, newest first. Soft-deleted and hidden tickets are excluded by default.
Scope required: tickets.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
status | string | Filter by exact status value (e.g. "Waiting for Parts") |
location_id | string | Filter to a specific location |
customer_email | string | Filter to one customer’s tickets by email address (case-insensitive). This is the reliable way to list a customer’s tickets, see note below. |
customer_id | integer | Deprecated. Resolves through an internal table that is never populated, so this filter always returns an empty list. Use customer_email instead. Still validated as a positive integer ≤ Number.MAX_SAFE_INTEGER; values above that return 400 invalid_field. |
is_mailin | boolean | true for mail-in only, false for walk-in only. Accepts true/false/1/0/yes/no; any other value returns 400 invalid_filter |
q | string | Search by order ID, customer name, email, phone, or device |
updated_since | string | ISO-8601 or Unix epoch, only tickets updated at or after this time |
include_deleted | boolean | Include soft-deleted tickets (default: false). Accepts true/false/1/0/yes/no; any other value returns 400 invalid_filter |
include_hidden | boolean | Include hidden tickets (default: false). Accepts 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 |
Filtering by customer: Use
customer_emailto list a customer’s tickets. Thecustomer_idfilter is deprecated, it resolves through a table that is never populated and will always return an empty list.
curl "https://app.benchkey.com/api/v1/tickets?status=Waiting%20for%20Parts&limit=10" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const params = new URLSearchParams({ status: "Waiting for Parts", limit: "10" });const res = await fetch( `https://app.benchkey.com/api/v1/tickets?${params}`, { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "ticket", "id": "TK-10042", "internal_id": "10042", "status": "Waiting for Parts", "priority": null, "device": "iPhone 15 Pro", "ticket_type": null, "is_mailin": false, "location_id": "1", "assigned_to": "alex@repairshop.com", "source": "walk-in", "customer": { "object": "ticket_customer", "name": "Jane Smith", "email": "jane.smith@example.com", "phone": "555-867-5309" }, "amount_paid": { "amount_cents": 8999, "amount": "89.99", "currency": "USD" }, "due_at": null, "closed_at": null, "created_at": "2025-11-01T10:14:22.000Z", "updated_at": "2025-11-03T16:45:00.000Z" } ], "has_more": false, "next_cursor": null}See Pagination for how to page through results.
Retrieve a ticket
Section titled “Retrieve a ticket”GET /api/v1/tickets/:idReturns a single ticket, including its attached devices array. The :id can be the canonical order_id, the ticket’s internal_id, or a queue ticket_number.
Scope required: tickets.read
curl "https://app.benchkey.com/api/v1/tickets/TK-10042" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/tickets/TK-10042", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const ticket = await res.json();Response
Section titled “Response”Returns the ticket object including the devices array. Returns 404 if no ticket with that ID exists.
Create a ticket
Section titled “Create a ticket”POST /api/v1/ticketsCreates a ticket via the same path the app uses, so queue routing, SLA timers, the audit log, and the ticket.created notification all fire. Returns 201 on success.
Scope required: tickets.write
An Idempotency-Key of 8 to 255 characters is required for ticket creation. Reuse it only for an identical retry. See Idempotency.
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
customer_name | yes | Customer’s full name |
customer_phone | yes* | Phone number (*required if no customer_email) |
customer_email | yes* | Email address (*required if no customer_phone) |
service_name | yes | Service/issue description (e.g. "Screen replacement") |
device_name | no | Device model (e.g. "iPhone 15 Pro") |
device_category | no | Device category |
category_id | no | Internal category ID |
task_type | no | 1 = mail-in, 2 = walk-in (default) |
assigned_to | no | Tech email to assign the ticket to |
queue_name | no | Name of the queue to route the ticket into |
location_id | no | Location ID |
imei | no | Device IMEI |
serial | no | Device serial number |
security_code | no | Device security code / PIN |
notes | no | Additional notes |
price | no | Quoted price (string, e.g. "149.99") |
how_did_you_find_us | no | Referral source |
address1 | no | Customer street address line 1 |
address2 | no | Customer street address line 2 |
city | no | City |
state | no | State / province |
zip | no | Postal code |
lead_id | no | ID of the lead this ticket was converted from |
extra_devices | no | Array of additional device objects to attach to the ticket. Each item must include at least one of: device_name, serial, imei, issue, service_name. Optional fields: make, model, device_type. All values must be strings. |
custom_fields | no | Object of tenant-defined New Ticket custom field values, keyed by field_key. Values must be strings, numbers, booleans, or null. Blank keys and reserved object-prototype keys (__proto__, constructor, prototype) are rejected. |
curl -X POST https://app.benchkey.com/api/v1/tickets \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-ticket-$(uuidgen)" \ -d '{ "customer_name": "Jane Smith", "customer_phone": "555-867-5309", "customer_email": "jane.smith@example.com", "service_name": "Screen replacement", "device_name": "iPhone 15 Pro", "notes": "Cracked front glass, back intact" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/tickets", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ customer_name: "Jane Smith", customer_phone: "555-867-5309", customer_email: "jane.smith@example.com", service_name: "Screen replacement", device_name: "iPhone 15 Pro", notes: "Cracked front glass, back intact", }),});const ticket = await res.json(); // HTTP 201Response
Section titled “Response”Returns the ticket object with HTTP 201.
Update a ticket
Section titled “Update a ticket”PATCH /api/v1/tickets/:idUpdates a ticket’s mutable fields. Send any subset of the fields below; omitted fields are left unchanged. An empty string ("") clears a field.
Side effects fire just as they would for a staff edit: changing assigned_to adds the assignee as a watcher, broadcasts the assignment notification, and reassigns open commission rows; changing customer/device/address keeps the queue in sync and logs the edit.
To change status, use Change status instead, status changes are workflow transitions, not field patches.
Scope required: tickets.write
Request body
Section titled “Request body”At least one of the following fields must be included:
| Field | Description |
|---|---|
assigned_to | Tech email (empty string or null to unassign) |
customer_name | Customer’s full name |
customer_email | Customer’s email address |
customer_phone | Customer’s phone number |
device | Device description |
address1 | Street address line 1 |
address2 | Street address line 2 |
city | City |
state | State / province |
zip | Postal code |
curl -X PATCH "https://app.benchkey.com/api/v1/tickets/TK-10042" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "assigned_to": "alex@repairshop.com", "device": "iPhone 15 Pro Max" }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/tickets/TK-10042", { method: "PATCH", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ assigned_to: "alex@repairshop.com" }),});const ticket = await res.json();Response
Section titled “Response”Returns the updated ticket object.
Change a ticket’s status
Section titled “Change a ticket’s status”POST /api/v1/tickets/:id/statusTransitions a ticket to a new status. Uses the same path the app uses, so every side effect fires: queue routing (mail-in arrival auto-moves), the status-history/activity log, portal phase sync, and any status-triggered notifications (e.g. the package-received email/SMS when a mail-in arrives).
Statuses are tenant-configurable, send a status string from your shop’s workflow. Discover the valid strings with GET /statuses. A status the tenant’s workflow rejects is returned as a 400.
Scope required: tickets.write
Send an Idempotency-Key header to make retries safe.
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
status | yes | The new status (e.g. "Ready for Pickup") |
curl -X POST "https://app.benchkey.com/api/v1/tickets/TK-10042/status" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: status-TK-10042-ready-$(date +%s)" \ -d '{ "status": "Ready for Pickup" }'Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/tickets/TK-10042/status", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ status: "Ready for Pickup" }), });const ticket = await res.json();Response
Section titled “Response”Returns the updated ticket object.
Add a note to a ticket
Section titled “Add a note to a ticket”POST /api/v1/tickets/:id/notesAdds a note to a ticket. Uses the same path the app uses, so @mention notifications, bin-location auto-detection, and the note.added automation event all fire.
Set customer_visible: true to share the note on the customer portal and email it to the customer. Customer-visible notes are rate-limited upstream; if the limit is hit the API returns 429 with a Retry-After header (seconds to wait before retrying).
Scope required: tickets.write
Send an Idempotency-Key header to make retries safe.
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
note | yes | The note text (must be a string; a present non-string value returns 400 invalid_field, absent/empty returns 400 missing_field) |
customer_visible | no | Boolean, share on the customer portal and email the customer (default: false). Must be a JSON boolean (true/false); non-boolean values return 400 invalid_field |
curl -X POST "https://app.benchkey.com/api/v1/tickets/TK-10042/notes" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: note-TK-10042-$(uuidgen)" \ -d '{ "note": "Screen ordered from supplier. ETA 2 days.", "customer_visible": false }'Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/tickets/TK-10042/notes", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ note: "Screen ordered from supplier. ETA 2 days.", customer_visible: false, }), });const note = await res.json(); // HTTP 201Response
Section titled “Response”Returns a note object with HTTP 201:
{ "object": "ticket_note", "id": "4812", "ticket_id": "TK-10042", "note": "Screen ordered from supplier. ETA 2 days.", "author": "alex@repairshop.com", "customer_visible": false, "pinned": false, "created_at": "2025-11-03T16:45:00.000Z"}Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_query | A list filter parameter (including updated_since) was supplied as an array or object instead of a single scalar value |
400 | invalid_filter | A boolean filter (is_mailin, include_deleted, include_hidden) was supplied with an unrecognized value. Accepted: true/false/1/0/yes/no |
400 | missing_field | A required field is absent (customer_name, service_name, status, note) |
400 | missing_contact | Neither customer_phone nor customer_email was provided |
400 | invalid_field | A field value is invalid (e.g. non-string value for a string field like customer_name, malformed email, non-integer customer_id) |
400 | no_updatable_fields | PATCH body contains no recognized updatable fields |
400 | invalid_request | The internal route rejected the request (e.g. status not in tenant workflow) |
404 | not_found | No ticket with that ID exists for this tenant |
409 | conflict | The request conflicts with the ticket’s current state |
422 | create_failed | Ticket creation could not be completed |
429 | rate_limited | Customer-visible note rate limit exceeded; includes Retry-After header (seconds) |
403 | insufficient_scope | API key lacks tickets.read or tickets.write |
See Errors for the full error envelope format.