Developers

The Crush It Engine REST API

Everything your receptionist knows, the moment it happens — calls, leads, texts, voicemails, and appointments as real-time webhooks — plus compliance-gated texting, contact upserts, live agent knowledge updates, and call-routing controls. One API key, included on every plan.

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 +1 automatically.
  • 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_url must be HTTPS.
  • filter is 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, and business_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 Gone permanently 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.

FieldRequiredDescription
toYesCustomer phone number, any format.
messageYesMessage 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.

FieldRequiredDescription
phone_numberYesAny format; normalized to E.164.
caller_full_nameContact name.
business_nameTheir company, if B2B.
business_typeTheir industry.
emailEmail address.
service_areaWhere they are.
product_interestWhat they want.
lead_statushot, warm, or cold.
call_summarySummary text.
notesFree-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.

FieldRequiredDescription
factYesThe 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.

FieldRequiredDescription
pausedYestrue 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.

FieldRequiredDescription
modeYesai_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.

FieldRequiredDescription
phoneYesAny format.
labelA 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).

FieldRequiredDescription
phoneYesAny 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.

FieldRequiredDescription
phoneYesAny 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.

FieldRequiredDescription
activeYestrue 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.

FieldRequiredDescription
starts_atYesISO 8601 date-time.
ends_atISO 8601 date-time.
customer_nameCustomer name.
customer_phoneAny format.
serviceWhat the appointment is for.
external_refYour 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.

FieldRequiredDescription
ratingYesup 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.

FieldRequiredDescription
actionYesanswer or dismiss.
answerRequired 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.

FieldRequiredDescription
phoneYesCustomer number (or pass id).
ai_activeYesfalse = 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.

FieldRequiredDescription
open_hour0–23, local.
close_hour1–24, local; must be after open_hour.
days_isoArray 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).

FieldRequiredDescription
phoneYesAny 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.

FieldRequiredDescription
template_open_hoursRescue text during business hours.
template_closed_hoursRescue text after hours.
rescue_delay_seconds0–300.
followups_enabledtrue/false.
followup_delay_minutes30–360.
next_day_reengage_enabledtrue/false.
followup_textFollow-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.

FieldRequiredDescription
idOne 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.

Developer API — Crush It Engine · Crush It Engine