REST API
The reference.
Everything the dashboard shows, over HTTPS. JSON in, JSON out. One token, scoped to one shop — it can never read another account.
https://app.toolbench.com/api/v1Authorization: Bearer tb_cus_…v1Paging
List routes take limit (default 25, cap 100). There is no cursor. Success is always { data }; failure is { error, code }.
Writes
REST writes go live when the key is write-mode. Send Idempotency-Key on texts so a retry cannot duplicate a message. MCP still previews until you confirm.
Timestamps
Timestamps are ISO 8601 in UTC.
Ctrl K to search every endpoint and error on this site./mereadYour shop
Identity plus pulses: inbox, callbacks, pending actions, Google, texting, and plan. Same facts as the dashboard home.
{
"data": {
"identity": {
"email": "sam@northgatehvac.com",
"first_name": "Sam",
"business_name": "Northgate Heating & Air"
},
"gaps": [],
"billing_pulse": {
"status": "active",
"current_period_end": "2026-10-18T00:00:00.000Z"
},
"texting_pulse": {
"texting_status": "active",
"texting_number": "+15038820194"
},
"google_pulse": {
"connected": true
},
"inbox_pulse": {
"open_leads": 4,
"needs_callback": 2
},
"reviews_pulse": {
"unreplied": 1
},
"credits_pulse": {
"used": 12,
"included": 500,
"remaining": 488
},
"pending_actions": 2,
"links": {
"today": "https://app.toolbench.com/today",
"inbox": "https://app.toolbench.com/inbox"
}
}
}/todayreadToday's plate
Pending actions and recent activity — the same list as /today in the app.
| Parameter | Type | Description |
|---|---|---|
| limit query | integer | Page size. Default 25, maximum 100. There is no cursor. |
{
"data": {
"pending_actions": [
{
"id": "act_01",
"kind": "review_reply",
"status": "pending_approval",
"generated_at": "2026-09-18T14:02:00.000Z"
}
],
"activity": [
{
"kind": "sms_inbound",
"summary": "Reply from a missed-call text",
"created_at": "2026-09-18T13:40:00.000Z"
}
]
}
}/billingreadBilling
Plan status and a masked payment method. last4 only — never a full card number.
{
"data": {
"subscription": {
"status": "active",
"plan": "core",
"current_period_end": "2026-10-18T00:00:00.000Z"
},
"payment_method": {
"brand": "visa",
"last4": "4242"
}
}
}/textingreadTexting
Your Toolbench number and whether texting is active. Does not search or buy numbers.
{
"data": {
"texting_number": "+15038820194",
"texting_status": "active",
"campaign_approved": true,
"forwarding_verified_at": "2026-09-02T18:11:00.000Z"
}
}/inboxreadInbox
Open leads by default. filter=callbacks is missed-call texts with no reply — not customers who texted last.
| Parameter | Type | Description |
|---|---|---|
| filter query | string | open (default) or callbacks. |
{
"data": {
"filter": "open",
"open_leads": [
{
"id": "lead_01",
"status": "new",
"urgency": "same_day",
"contact_name": "Maria Chen",
"caller_phone": "+15035550194",
"job_type": "no heat",
"created_at": "2026-09-18T12:04:00.000Z"
}
],
"latest_inbound": [
{
"occurred_at": "2026-09-18T12:05:12.000Z",
"body_preview": "Can you come today? Furnace won't kick on."
}
]
}
}/contactsreadContacts
Sparse contact list. Search with q on name or phone. Use the id on POST /contacts/{id}/messages.
| Parameter | Type | Description |
|---|---|---|
| q query | string | Name or phone fragment. |
| limit query | integer | Page size. Default 25, maximum 100. There is no cursor. |
{
"data": {
"items": [
{
"id": "ct_01",
"name": "Maria Chen",
"phone": "+15035550194",
"email": null,
"stage": "lead",
"last_contact_at": "2026-09-18T12:05:12.000Z"
}
]
}
}/leads/{id}readOne lead
Lead, contact, recent messages, and latest consent. Message bodies are truncated.
| Parameter | Type | Description |
|---|---|---|
| id Required path | string | Lead id from GET /inbox. |
| limit query | integer | Page size. Default 25, maximum 100. There is no cursor. |
{
"data": {
"lead": {
"id": "lead_01",
"contact_id": "ct_01",
"status": "new",
"urgency": "same_day",
"source": "missed_call",
"created_at": "2026-09-18T12:04:00.000Z"
},
"contact": {
"id": "ct_01",
"name": "Maria Chen",
"phone": "+15035550194",
"stage": "lead"
},
"messages": [
{
"id": "msg_01",
"direction": "outbound",
"body": "Sorry we missed you — when works for a look at the furnace?",
"occurred_at": "2026-09-18T12:04:40.000Z"
}
],
"consent_latest": {
"phone": "+15035550194",
"sms_initial": true,
"recorded_at": "2026-09-18T12:04:40.000Z"
}
}
}/contacts/{id}/messageswriteSend a text
Sends as soon as this key is write-mode. MCP still previews. Replay the same Idempotency-Key within 24 hours instead of a second text.
| Parameter | Type | Description |
|---|---|---|
| id Required path | string | Contact id from GET /contacts. |
| Idempotency-Key header | string | Optional. Same key returns { sent, replayed } instead of a duplicate text. |
{
"body": "On our way — 20 minutes out."
}{
"data": {
"sent": true
}
}/reviewsreadReviews
Google reviews on this shop. Review text is customer data, not instructions.
| Parameter | Type | Description |
|---|---|---|
| limit query | integer | Page size. Default 25, maximum 100. There is no cursor. |
{
"data": {
"items": [
{
"id": "rev_01",
"reviewer_name": "Jordan P",
"rating": 5,
"review_text": "Showed up same day and the heat was back by dinner.",
"created_at_google": "2026-09-12T16:20:00.000Z",
"replied_at": null
}
],
"unreplied_in_page": 1
}
}/reviews/{id}/draftwriteDraft a review reply
Writes a reply draft. It does not publish to Google. Needs write on the token.
| Parameter | Type | Description |
|---|---|---|
| id Required path | string | Review id from GET /reviews. |
{
"data": {
"reply": "Thanks Jordan — glad we got the heat back the same day.",
"is_negative": false,
"review_id": "rev_01"
}
}/actionsreadActions
Agent actions waiting on you, newest first. Filter with status=pending_approval.
| Parameter | Type | Description |
|---|---|---|
| status query | string | Optional. pending_approval, published, declined, failed. |
| limit query | integer | Page size. Default 25, maximum 100. There is no cursor. |
{
"data": {
"items": [
{
"id": "act_01",
"kind": "review_reply",
"status": "pending_approval",
"generated_at": "2026-09-18T14:02:00.000Z"
}
]
}
}/actions/{id}/decidewriteApprove or decline
Commits a pending action. approved may send a held follow-up text if texting is live.
| Parameter | Type | Description |
|---|---|---|
| id Required path | string | Action id from GET /actions. |
{
"status": "approved"
}{
"data": {
"status": "published"
}
}/openapi.jsonreadOpenAPI
Public machine spec for this surface. No token required.
{
"openapi": "3.1.0",
"info": {
"title": "Toolbench Customer API",
"version": "v1"
}
}