Overview
The API is plain HTTPS + JSON. All endpoints live under one base URL:
https://crushitengine.com/api/v1
There are three kinds of endpoints: webhooks (we POST events to your URL as they happen), actions (you change something), and searches (you look something up). Everything is scoped to your business — an API key can only ever see and touch its own account.
Authentication
Every request sends your API key as a bearer token. Generate it (or rotate it) in the dashboard under Settings → Lead delivery → Zapier & API. The same key powers the Zapier app and this API.
curl https://crushitengine.com/api/v1/me \ -H "Authorization: Bearer YOUR_API_KEY"
- A bad or missing key returns
401. - Rotating the key invalidates the old one immediately and disconnects any webhook subscriptions — resubscribe with the new key.
- Treat the key like a password: server-side only, never in a browser or an app you ship.
Conventions & errors
- Request and response bodies are JSON. Send
Content-Type: application/json. - Phone numbers are accepted in any format and normalized to E.164 (
+18135551234). US 10-digit numbers get+1automatically. - Timestamps are ISO 8601, UTC.
- Errors return
{ "ok": false, "error": "…" }with a 400 (your input), 401 (your key), or 500 (us) status. Success responses carry"ok": true. - A compliance-blocked text is not an error — see the compliance model.
- No hard rate limits today; keep it civil (bursts under a few requests per second). Abusive traffic gets keys revoked.
Webhooks
Subscribe a URL to an event and we POST the payload the moment it happens. Subscribe once per event (up to 100 active subscriptions):
POST/hooks
curl -X POST https://crushitengine.com/api/v1/hooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "lead_captured",
"target_url": "https://example.com/hooks/voicedesk",
"filter": { "lead_status": "hot" }
}'
→ { "ok": true, "id": "SUBSCRIPTION_ID" }target_urlmust be HTTPS.filteris optional: string equality against top-level payload fields, case-insensitive. The example only delivers hot leads.- Every delivery is a JSON POST wrapped in a stable envelope:
id(unique per event — deduplicate on it),event, andbusiness_id, plus the event's own fields. - Delivery is at-most-once: one POST, 5-second timeout, no automatic retries. If your endpoint must not miss data, also poll the searches, or use Zapier, which manages retries for you.
- Respond 2xx quickly. Responding
410 Gonepermanently deletes the subscription.
Unsubscribe with the id you got back:
DELETE/hooks/{id}
While building, fetch recent real examples of any event's payload, shaped exactly like live deliveries (falls back to a static sample when your account has no matching data yet):
GET/samples/{event}
Event reference
All 22 events, with example payloads. Every payload also carries the envelope fields event and business_id.
call_startedAn inbound call hit your number (fires at the start of the call, before any outcome is known).
{
"id": "00000000-0000-0000-0000-00000000000a",
"caller_number": "+18135551234",
"business_number": "+18135550000",
"at": "2026-09-08T15:04:00Z",
"trusted_caller": false
}call_completedA call ended and was processed — includes outcome, sentiment, duration, and the full transcript text.
{
"id": "00000000-0000-0000-0000-000000000001",
"call_id": "00000000-0000-0000-0000-000000000001",
"direction": "inbound",
"caller_number": "+18135551234",
"business_number": "+18135550000",
"started_at": "2026-09-08T15:04:05Z",
"duration_sec": 142,
"outcome": "lead",
"sentiment": "positive",
"sentiment_note": "Caller was excited to get scheduled.",
"transcript_text": "Agent: Thanks for calling! ...\nCaller: I need a quote...",
"recording_url": "https://crushitengine.com/api/recordings/example",
"returning_caller": false,
"prior_calls": 0
}missed_callA call went unanswered, or the caller hung up before speaking.
{
"id": "00000000-0000-0000-0000-000000000003",
"call_id": "00000000-0000-0000-0000-000000000003",
"caller_number": "+18135551234",
"business_number": "+18135550000",
"at": "2026-09-08T15:10:00Z",
"reason": "no_answer"
}voicemail_receivedA voicemail was recorded on a missed call, with its transcription and recording link.
{
"id": "00000000-0000-0000-0000-00000000000b",
"call_id": "00000000-0000-0000-0000-000000000003",
"caller_number": "+18135551234",
"transcription": "Hi, this is Maria — call me back about the estimate.",
"recording_url": "https://example.telnyx.com/recordings/example.mp3",
"at": "2026-09-08T15:12:00Z"
}lead_capturedThe AI receptionist captured a lead — name, contact details, what they wanted, and a call summary.
{
"id": "00000000-0000-0000-0000-000000000002",
"lead_id": "00000000-0000-0000-0000-000000000002",
"caller_full_name": "Maria Torres",
"business_name": "Torres Landscaping",
"business_type": "landscaping",
"phone_number": "+18135551234",
"email": "maria@example.com",
"service_area": "Tampa, FL",
"product_interest": "Weekly lawn care",
"lead_status": "hot",
"call_summary": "Wants weekly service for a half-acre lot, ready to start.",
"notes": "Prefers a morning call back.",
"source": "angi",
"channel": "angi",
"lead_cost_cents": 3800,
"external_ref": "ANGI-10293",
"created_at": "2026-09-08T15:06:00Z"
}rescue_sentA missed-call text-back message went out. `touch` is rescue, followup, or reengage; `source` is template or ai_drafted.
{
"id": "00000000-0000-0000-0000-000000000004",
"caller_number": "+18135551234",
"body": "Sorry we missed your call! This is Tampa Test Plumbing - how can we help?",
"source": "template",
"touch": "rescue",
"first_contact": true,
"thread_id": "00000000-0000-0000-0000-000000000005",
"at": "2026-09-08T15:10:45Z"
}caller_repliedA customer texted back for the first time on a conversation thread.
{
"id": "00000000-0000-0000-0000-000000000005",
"thread_id": "00000000-0000-0000-0000-000000000005",
"caller_number": "+18135551234",
"body": "Yes! I need someone out this week.",
"at": "2026-09-08T15:14:00Z"
}sms_receivedAny inbound text message from a customer.
{
"id": "00000000-0000-0000-0000-00000000000c",
"thread_id": "00000000-0000-0000-0000-000000000005",
"caller_number": "+18135551234",
"body": "What time can you come out?",
"first_reply": false,
"at": "2026-09-08T15:16:00Z"
}opted_outA customer texted STOP (or was suppressed). Texting them is now blocked platform-wide.
{
"id": "00000000-0000-0000-0000-000000000006",
"caller_number": "+18135551234",
"action": "opted_out",
"at": "2026-09-08T15:20:00Z"
}opted_inA customer texted START and restored their texting consent.
{
"id": "00000000-0000-0000-0000-000000000007",
"caller_number": "+18135551234",
"action": "opted_in",
"at": "2026-09-08T15:25:00Z"
}unanswered_questionThe agent was asked something outside its knowledge — the exact question, ready to be answered and taught back.
{
"id": "00000000-0000-0000-0000-000000000008",
"question": "Do you install water softeners?",
"call_id": "00000000-0000-0000-0000-000000000001",
"at": "2026-09-08T15:30:00Z"
}appointment_bookedThe agent booked an appointment on a call.
{
"id": "00000000-0000-0000-0000-000000000009",
"appointment_id": "00000000-0000-0000-0000-000000000009",
"starts_at": "2026-09-10T14:00:00Z",
"ends_at": "2026-09-10T15:00:00Z",
"customer_name": "Maria Torres",
"customer_phone": "+18135551234",
"service": "Estimate visit",
"source": "angi",
"at": "2026-09-08T15:35:00Z"
}returning_callerA number that has called before called again — includes the prior-call count and last call date.
{
"id": "00000000-0000-0000-0000-00000000000d",
"call_id": "00000000-0000-0000-0000-000000000001",
"caller_number": "+18135551234",
"prior_calls": 2,
"last_call_at": "2026-09-01T12:00:00Z",
"at": "2026-09-08T15:04:05Z"
}negative_sentiment_callA completed call scored negative sentiment — a caller worth a personal follow-up.
{
"id": "00000000-0000-0000-0000-00000000000e",
"call_id": "00000000-0000-0000-0000-000000000001",
"caller_number": "+18135551234",
"sentiment": "negative",
"sentiment_note": "Caller was frustrated about a missed appointment.",
"at": "2026-09-08T15:40:00Z"
}transfer_failedA live transfer to you didn't connect (the agent fell back to taking a message/callback).
{
"id": "00000000-0000-0000-0000-00000000000f",
"call_id": "00000000-0000-0000-0000-000000000001",
"caller_number": "+18135551234",
"at": "2026-09-08T15:45:00Z"
}call_ratedYou rated a call thumbs up or down in the dashboard.
{
"id": "00000000-0000-0000-0000-000000000010",
"call_id": "00000000-0000-0000-0000-000000000001",
"rating": "down",
"caller_number": "+18135551234",
"at": "2026-09-08T15:50:00Z"
}review_receivedA new review synced in from a connected profile (e.g. Google) — includes platform, rating, author, and text.
{
"id": "00000000-0000-0000-0000-000000000011",
"platform": "google",
"author": "Maria Torres",
"rating": 5,
"text": "Alex answered right away and got someone out same day. Fantastic service!",
"at": "2026-09-08T16:00:00Z"
}negative_reviewA new review at or below 3 stars landed — worth a personal, timely response.
{
"id": "00000000-0000-0000-0000-000000000012",
"platform": "google",
"author": "Sam Vale",
"rating": 2,
"text": "Took a while to get scheduled.",
"at": "2026-09-08T16:05:00Z"
}review_request_sentA review request was scheduled/sent to a customer, with the review link and source.
{
"id": "00000000-0000-0000-0000-000000000013",
"caller_number": "+18135551234",
"customer_name": "Maria Torres",
"link": "https://g.page/r/example/review",
"source": "api",
"at": "2026-09-08T16:10:00Z"
}referral_sentA referral request was sent to a customer you served (Reputation campaigns).
{
"id": "00000000-0000-0000-0000-000000000014",
"caller_number": "+18135551234",
"customer_name": "Maria Torres",
"kind": "referral",
"source": "api",
"at": "2026-09-10T16:10:00Z"
}rebook_scheduledA repeat-booking reminder was scheduled for a past customer, with its send date (`scheduled_for`).
{
"id": "00000000-0000-0000-0000-000000000015",
"caller_number": "+18135551234",
"customer_name": "Maria Torres",
"kind": "rebook",
"source": "appointment",
"scheduled_for": "2027-03-10T16:10:00Z",
"at": "2026-09-10T16:10:00Z"
}lead_escalatedA texting customer was handed off to a human (asked for a person, urgent, or unhandled) — includes the reason and marketplace `source`.
{
"id": "00000000-0000-0000-0000-000000000016",
"thread_id": "00000000-0000-0000-0000-000000000005",
"caller_number": "+18135551234",
"customer_name": "Maria Torres",
"reason": "asked to speak with a person",
"channel": "sms",
"source": "angi",
"at": "2026-09-08T15:18:00Z"
}Actions
Verify your key
GET/me
Confirms the API key works and identifies the business it belongs to.
→ { "ok": true, "business_id": "…", "business_name": "Tampa Test Plumbing" }Send a text message
POST/send-text
Texts a customer from your business number. Every send passes the same compliance gate as the platform's own messages — opt-outs and quiet hours are re-checked at the instant of send, and unregistered numbers never send into carrier filtering.
| Field | Required | Description |
|---|---|---|
| to | Yes | Customer phone number, any format. |
| message | Yes | Message text (up to 1,000 characters). |
→ { "ok": true, "status": "sent", "to": "+18135551234", "thread_id": "…" }A blocked send is NOT an error — you get 200 with `status: "suppressed"` and a `reason`: `contact_opted_out`, `quiet_hours`, or `carrier_registration_pending`. Branch on `status`, don't retry.
Sent messages appear in your dashboard inbox thread like any other outbound text.
Create or update a contact/lead
POST/contacts
Phone-keyed upsert into your leads. If a lead with that phone number exists, only the fields you send are overwritten; otherwise a new lead is created.
| Field | Required | Description |
|---|---|---|
| phone_number | Yes | Any format; normalized to E.164. |
| caller_full_name | — | Contact name. |
| business_name | — | Their company, if B2B. |
| business_type | — | Their industry. |
| — | Email address. | |
| service_area | — | Where they are. |
| product_interest | — | What they want. |
| lead_status | — | hot, warm, or cold. |
| call_summary | — | Summary text. |
| notes | — | Free-form notes. |
→ { "ok": true, "created": true, "lead": { … } }Teach the agent a fact
POST/knowledge
Adds a fact to your live agent's knowledge as a coaching note and republishes the agent — the change is live on the very next call, usually within seconds.
| Field | Required | Description |
|---|---|---|
| fact | Yes | The fact, in plain English (5–500 characters). |
→ { "ok": true, "status": "applied_live", "note_id": "…" }If the live republish can't complete, you get `status: "saved_pending"` — the note is saved and applies on the next Save & publish from the dashboard.
Pause or resume the AI
POST/pause
One-tap pause as an API: while paused, calls ring your real phone and text-back still rescues the ones you miss.
| Field | Required | Description |
|---|---|---|
| paused | Yes | true to pause, false to resume. |
→ { "ok": true, "paused": true }Pausing requires a transfer phone set in Settings — paused calls have to ring somewhere.
Set the answering mode
POST/answering-mode
Switches how your number answers, exactly like the dashboard switch.
| Field | Required | Description |
|---|---|---|
| mode | Yes | ai_always, mctb_always, ai_after_hours, or mctb_after_hours. |
→ { "ok": true, "mode": "ai_after_hours" }Add a trusted number
POST/trusted-numbers
Trusted callers skip the AI and ring you directly, and are never sent text-back messages. 25 numbers max.
| Field | Required | Description |
|---|---|---|
| phone | Yes | Any format. |
| label | — | A name for the entry (up to 60 characters). |
→ { "ok": true, "entry": { … } }Remove a trusted number
DELETE/trusted-numbers
Removes a number from the trusted list (send the phone in the JSON body).
| Field | Required | Description |
|---|---|---|
| phone | Yes | Any format. |
→ { "ok": true, "removed": "+18135551234" }Suppress a contact (do not text)
POST/suppress
Marks a number as opted out here — use it to sync unsubscribes from your CRM or email platform so no channel texts someone who said stop anywhere.
| Field | Required | Description |
|---|---|---|
| phone | Yes | Any format. |
→ { "ok": true, "suppressed": "+18135551234" }One-way by design: under FCC revocation rules only the customer texting START restores consent, so there is no unsuppress endpoint.
Turn text-back on or off
POST/text-back
Flips the missed-call rescue engine — for example, off while a vacation auto-responder runs.
| Field | Required | Description |
|---|---|---|
| active | Yes | true or false. |
→ { "ok": true, "active": false }Create an appointment
POST/appointments
Writes a confirmed appointment in from your booking tool. Callers with an upcoming appointment are automatically spared sales-y rescue texts.
| Field | Required | Description |
|---|---|---|
| starts_at | Yes | ISO 8601 date-time. |
| ends_at | — | ISO 8601 date-time. |
| customer_name | — | Customer name. |
| customer_phone | — | Any format. |
| service | — | What the appointment is for. |
| external_ref | — | Your system's id, for cross-referencing. |
→ { "ok": true, "appointment": { … } }Appointments created here never emit the appointment_booked webhook — no event loops.
Rate a call
POST/calls/{id}/rate
Rate a call as the owner. Down-ratings flag the call for quality review.
| Field | Required | Description |
|---|---|---|
| rating | Yes | up or down. |
→ { "ok": true, "call_id": "…", "rating": "down" }Answer or dismiss a question
POST/questions/{id}
Close an open unanswered question. Answering turns your answer into agent knowledge and republishes the live agent — the teach-it loop as an API.
| Field | Required | Description |
|---|---|---|
| action | Yes | answer or dismiss. |
| answer | — | Required for answer: the answer in plain English (3–400 chars). |
→ { "ok": true, "question_id": "…", "status": "applied_live" }Take over / hand back a conversation
POST/thread
Toggle who handles one text thread. Taking over (ai_active=false) stops AI replies and cancels queued follow-ups for that customer.
| Field | Required | Description |
|---|---|---|
| phone | Yes | Customer number (or pass id). |
| ai_active | Yes | false = owner handles it; true = AI resumes. |
→ { "ok": true, "thread_id": "…", "ai_handling": false }Set business hours
POST/business-hours
Local hours that drive after-hours answering modes and what the agent tells callers. Republishes the live agent.
| Field | Required | Description |
|---|---|---|
| open_hour | — | 0–23, local. |
| close_hour | — | 1–24, local; must be after open_hour. |
| days_iso | — | Array of days, 1=Mon … 7=Sun. |
→ { "ok": true, "hours": { … }, "agent_republished": true }Set the transfer number
POST/transfer-number
Where hot calls and pass-through modes ring. Republishes the live agent (the number is baked into its transfer tool).
| Field | Required | Description |
|---|---|---|
| phone | Yes | Any format. |
→ { "ok": true, "transfer_number": "+1813…", "agent_republished": true }Update text-back settings
POST/textback-settings
Partial update — only fields you send change. Quiet hours are a fixed compliance floor and not settable.
| Field | Required | Description |
|---|---|---|
| template_open_hours | — | Rescue text during business hours. |
| template_closed_hours | — | Rescue text after hours. |
| rescue_delay_seconds | — | 0–300. |
| followups_enabled | — | true/false. |
| followup_delay_minutes | — | 30–360. |
| next_day_reengage_enabled | — | true/false. |
| followup_text | — | Follow-up message text. |
→ { "ok": true, "updated": ["rescue_delay_seconds"] }Cancel queued follow-ups
DELETE/followups
Cancel one queued follow-up by id, or everything queued for a phone number.
| Field | Required | Description |
|---|---|---|
| id | — | One follow-up's id. |
| phone | — | …or a customer number (cancels all queued for them). |
→ { "ok": true, "cancelled": 2 }Cancel an appointment
DELETE/appointments/{id}
Marks the appointment cancelled (kept in history).
→ { "ok": true, "appointment_id": "…", "status": "cancelled" }Reads & searches
All reads are GETs with query parameters. List endpoints return { ok, … } objects; the four phone-keyed find/consent searches return a bare JSON array (possibly empty, never an error for "not found").
Activity stats
GET/stats
?period=today|7d|30d|this_month|last_month
Calls, answered/missed, minutes, leads, appointments, outcome and sentiment breakdowns for the period — with the equal-length prior period alongside for comparison.
List calls
GET/calls
?from=&to=&outcome=&limit=&offset=
Calls newest-first with outcome, sentiment, rating, and a short transcript preview. Paginated (limit up to 100).
Get a call
GET/calls/{id}
One call in full: complete transcript text, sentiment with reason, duration, rating, recording link.
Caller profile
GET/caller
?phone=8135551234
Everything about one number in one read: name, call history with previews, lead record, text consent and recent messages, upcoming appointments, trusted status.
List leads
GET/leads
?from=&to=&status=hot|warm|cold&source=angi|thumbtack|yelp|lsa|meta&limit=&offset=
Captured leads newest-first, paginated. Filter by marketplace `source`; rows include `source`, `lead_cost_cents`, and `external_ref`.
Get a lead
GET/leads/{id}
One lead in full, including delivery history (where it was sent and whether it succeeded).
List text threads
GET/threads
?limit=
The text inbox: recent conversations with last-message previews, AI-vs-owner handling, and opt-out status.
Read a conversation
GET/thread
?phone=8135551234 (or ?id=)
One full text conversation, oldest message first.
List voicemails
GET/voicemails
?limit=
Recent voicemails with transcriptions and recording links.
Unanswered questions
GET/questions
Open questions the agent couldn't answer — the teach-loop inbox.
Account setup
GET/setup
The whole configuration in one read: business and hours, agent, routing and answering mode, text-back settings, compliance posture.
Agent knowledge
GET/knowledge
What the live agent knows: knowledge base, pronunciation notes, coaching notes with statuses.
Pending follow-ups
GET/followups
Queued follow-up texts that haven't sent yet, with due times.
Usage & pacing
GET/usage
This month's minutes vs. the plan, with an end-of-month projection.
Do-not-text registry
GET/opt-outs
Every contact who has opted out of texting.
List appointments
GET/appointments
?from=&to=&status=
Appointments soonest-first (defaults to upcoming confirmed).
Find calls
GET/find-call
?phone=8135551234
The 5 most recent calls from a phone number, with outcome, sentiment, transcript text, and recording link.
Find a contact/lead
GET/find-lead
?phone=8135551234 or ?email=maria@example.com
The 5 most recent matching leads.
Find appointments
GET/find-appointment
?phone=8135551234
Upcoming confirmed appointments for a phone number.
Check text consent
GET/consent
?phone=8135551234
Whether this number may be texted — `can_text`, `opted_out`, and whether it's a known contact. Check before texting anyone from any tool.
Compliance model
Texting through this API is impossible to do non-compliantly, on purpose. Every outbound message re-checks, at the instant of send:
- Opt-outs. A customer who texted STOP is blocked everywhere until they text START. Suppressions are one-way from the API.
- Quiet hours. No automated texts outside 8am–8pm in the business's local time.
- Carrier registration. Nothing sends until the business's A2P registration is approved — unregistered traffic gets carrier-filtered and can burn the number, so we suppress it instead.
Blocked sends return 200 with status: "suppressed" and the reason, and every refusal is written to the account's append-only compliance audit trail. Use GET /consent before texting anyone from your own tools.
MCP (AI assistants)
The platform is also a remote Model Context Protocol (MCP) server, so AI assistants like Claude can work your account directly — "text my last missed caller back," "what did people call about this week?", "teach my receptionist that we now do water softeners." It exposes 41 tools: every read, search, and action on this page — activity stats, caller profiles, the text inbox, the teach-it loop, and more — with the same compliance gates as everything else.
https://crushitengine.com/api/mcp
Authenticate with your same API key. From Claude Code, for example:
claude mcp add --transport http voicedesk \ https://crushitengine.com/api/mcp \ --header "Authorization: Bearer YOUR_API_KEY"
For clients that can't send headers, append ?key=YOUR_API_KEY to the URL — but prefer the header where possible, since URLs tend to end up in logs.
Prefer Zapier?
Everything on this page also exists as a Zapier app — the same events as instant triggers and the same actions and searches as Zap steps, no code required, with Zapier handling delivery retries. Connect it from Settings → Lead delivery in your dashboard using the same API key.
Questions, or need something the API doesn't cover yet? Tell us in the Help Center — integration requests get read.