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 with AI Voice Scale.

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 Integrations → Automation. 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), 403 (your plan or products don't include that action), 404 (not found), 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/crushitengine",
    "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.
  • Webhooks only fire while the account is on AI Voice Scale. After a downgrade your subscriptions are kept but nothing is sent until the account is back on Scale.
  • 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 27 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.",
  "topics": [
    "Water Heater",
    "Quote Request"
  ],
  "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_capturedA new lead came in — from the AI on a call, the team phone, a marketplace (Speed-to-Lead), your website widget (“Text us”, or a call-back request from the “Ask us” assistant), or your digital business card. Includes contact details, what they wanted, a summary, the `source`, who referred them (`referred_by`), and for card leads the intake answers.
{
  "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",
  "referred_by": "Dave Kim",
  "card_id": null,
  "card_name": null,
  "intake": null,
  "emergency": false,
  "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; `channel` is sms, or email when it went by the email backup (landline or bounced text).
{
  "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",
  "channel": "sms",
  "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?",
  "photo_count": 0,
  "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_bookedAn appointment was booked — by the AI on a call or in a text conversation, or from your digital business card.
{
  "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"
}
appointment_requestedA caller wanted to book but no open time worked, so your AI took a request for the office to schedule. sent_to names the job software it landed in as an unscheduled job (Jobber, Housecall Pro or ServiceTitan), or is null.
{
  "id": "00000000-0000-0000-0000-000000000020",
  "customer_name": "Maria Torres",
  "customer_phone": "+18135551234",
  "service": "Water heater replacement",
  "preferred_time": "Saturday morning",
  "sent_to": "Jobber",
  "at": "2026-10-11T15:35:00Z"
}
appointment_cancelledAn appointment was cancelled (dashboard or API). Its reminder text is called off automatically; `waitlist_offered` says whether the next person on your waitlist was texted about the open slot.
{
  "id": "00000000-0000-0000-0000-000000000017",
  "appointment_id": "00000000-0000-0000-0000-000000000009",
  "starts_at": "2026-09-10T14:00:00Z",
  "customer_name": "Maria Torres",
  "customer_phone": "+18135551234",
  "service": "Estimate visit",
  "reminder_cancelled": true,
  "waitlist_offered": true,
  "cancelled_via": "dashboard",
  "at": "2026-09-09T11:00: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 ask was scheduled for a customer you served — after a job, or right after they left a 4–5★ review (`source` is appointment, happy_review, api, or manual). Part of Marketing (AI Voice Pro & Scale).
{
  "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`). Part of Marketing (AI Voice Pro & Scale).
{
  "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"
}
crew_updateSomeone on the team tapped a crew update in team chat — running late, parts needed, need help, or job done. Includes who sent it, the message, and the customer's number when it was sent in a customer conversation.
{
  "id": "00000000-0000-0000-0000-000000000018",
  "conversation_id": "00000000-0000-0000-0000-000000000019",
  "update": "parts_needed",
  "label": "Parts needed",
  "message": "Parts needed — need a 3/4 ball valve",
  "from": "Mike",
  "customer_number": "+18135551234",
  "at": "2026-09-27T15:10:00Z"
}
feedback_receivedA customer answered the private "how did we do? reply 1 to 5" check-in (Reputation). Includes the score, `low_score` (3 or under), and any comment.
{
  "id": "00000000-0000-0000-0000-000000000021",
  "customer_name": "Maria Torres",
  "customer_phone": "+18135551234",
  "score": 2,
  "low_score": true,
  "comment": "Tech was late and didn't call ahead",
  "at": "2026-10-12T15:35:00Z"
}
unbooked_callA caller clearly wanted a job, quote or appointment but the call ended without one — a second-chance lead. Includes the AI's reason (e.g. "price too high") and a summary.
{
  "id": "00000000-0000-0000-0000-000000000017",
  "call_id": "00000000-0000-0000-0000-000000000001",
  "caller_number": "+18135551234",
  "reason": "price too high",
  "summary": "Asked what a water heater replacement costs, then said they'd think about it.",
  "team_call": false,
  "at": "2026-09-08T15:50: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 — including its plan and the products it owns, so you know which actions apply.

→ { "ok": true, "business_id": "…", "business_name": "Tampa Test Plumbing", "plan": "scale", "products": ["ai_voice", "mctb"], "marketing_included": true }

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). Optional when media_url is set.
media_url—A public https link to one photo (JPG, PNG or GIF, up to 5 MB) — sends a picture text (MMS).
send_at—ISO date-time to send later instead of now (1 minute to 90 days ahead, words only). Returns `status: "scheduled"` and a `scheduled_id`. Opt-outs, quiet hours and registration are re-checked when it sends.
→ { "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.

Open or close a job's team chat

POST/job-chat

Automatic job chats from any field software: open a team chat for a dispatched job with the assigned techs (matched to team logins by email) plus the owner and managers, and a first message with the customer, address and job summary. Calling open again refreshes it; close marks it done and keeps the history. Team messaging comes with AI Voice or Cloud PBX; without either, this returns 403.

FieldRequiredDescription
actionYes"open" or "close".
job_idYesYour system's job id.
source—Where the job lives (jobber, housecall, servicetitan…). Defaults to "api".
job_number, title, customer_name, customer_phone, address, summary—Details shown in the chat's first message (open only).
assignee_emails—Array (or comma-separated) of the techs' emails; each who has a team login is added.
→ { "ok": true, "conversation_id": "…", "created": true }

Add a note to a customer

POST/customer-note

Adds a note to a customer's timeline — what was promised, a gate code, anything the team should see. Shows in the dashboard with who wrote it.

FieldRequiredDescription
phoneYesCustomer phone number, any format.
noteYesThe note (up to 4,000 characters).
author—Who wrote it. Defaults to "API".
→ { "ok": true, "phone": "+18135551234" }

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_name—Contact name.
business_name—Their company, if B2B.
business_type—Their industry.
email—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.

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 with Missed-Call Text Back, the ones you miss still get a text.

FieldRequiredDescription
pausedYestrue to pause, false to resume.
→ { "ok": true, "paused": true }

Pausing requires a transfer phone (Phone line → Transfer) — paused calls have to ring somewhere. With the Team Phone on, paused calls go to your menu and team instead, so no transfer phone is needed.

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" }

Every mode except ai_always uses text-back, so it needs Missed-Call Text Back on the account (403 otherwise). The two scheduled modes also need AI Voice Pro or Scale.

With the Team Phone (Cloud PBX) on, the phone system owns the number and a separate front-desk setting in the dashboard decides who answers — this returns 409 and changes nothing.

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.
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).

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 }

Turning it on needs Missed-Call Text Back on the account (403 otherwise). Turning it off always works.

Change appointment reminders

POST/reminder-settings

The reminder text every booked customer gets before their appointment — on by default, no text-back needed. GET the same path to read the current settings.

FieldRequiredDescription
enabled—true or false.
hours_before—2, 4, 24, or 48.
→ { "ok": true, "updated": ["reminder_hours_before"] }

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_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.

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

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_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).

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_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.
ai_callback_enabled—true/false — the AI receptionist calls missed callers back (needs AI Voice Pro or Scale with caller recognition on).
email_fallback_enabled—true/false — email the missed-call message instead when the caller is on a landline or the text bounces (only to an email you already have).
→ { "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
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), calls off its reminder text, offers the slot to the next person on your waitlist, and fires the appointment_cancelled webhook.

→ { "ok": true, "appointment_id": "…", "status": "cancelled" }

Send a review request

POST/review-request

Schedule the compliant post-job Google-review text to a customer (same link to everyone; opt-out, quiet-hours and A2P gated). Optional follow-up rides the queue.

FieldRequiredDescription
phoneYesCustomer mobile, any common format
name—Customer name (first name is used)
technician—Technician name for the {tech} template variable (`tech` also accepted)
→ { "ok": true, "status": "scheduled" }

If it can't be sent you get `status: "skipped"` with a `reason` (for example disabled, opted_out, already_sent) — not an error.

Send a referral ask

POST/referral-request

Text a customer you just served a friendly "know someone who could use us?" message (opt-out, quiet-hours and A2P gated like every send). Part of Marketing — AI Voice Pro & Scale — with referral asks switched on in Marketing → Referrals. One ask per customer every 90 days.

FieldRequiredDescription
phoneYesCustomer mobile
name—Customer name
→ { "ok": true, "status": "scheduled" }

Otherwise `status: "skipped"` with a `reason`: not_included (plan), disabled, already_sent, opted_out.

Schedule a repeat-booking reminder

POST/rebook

Queue a months-out "time to get back on the schedule" text. Part of Marketing — AI Voice Pro & Scale — with repeat-booking on in Marketing → Referrals. It's a marketing message, so it also needs the marketing-consent switch (Marketing → Campaigns).

FieldRequiredDescription
phoneYesCustomer mobile
name—Customer name
months—How many months out, 1–24 (default: the business's setting)
→ { "ok": true, "status": "scheduled" }

Otherwise `status: "skipped"` with a `reason`: not_included, disabled, no_marketing_consent, already_sent, opted_out.

Draft a review reply

POST/draft-review-response

AI drafts a reply to a given review for the OWNER to approve and post — it never writes review content itself, only responses.

FieldRequiredDescription
review_textYesThe review's text
rating—Its star rating, 1–5
author—The reviewer's name
→ { "ok": true, "draft": "…" }

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 (and how many were referred), appointments, outcome and sentiment breakdowns for the period — with the equal-length prior period alongside for comparison.

List calls

GET/calls

?from=&to=&outcome=&q=&topic=&limit=&offset=

Calls newest-first with outcome, sentiment, topics, rating, and a short transcript preview. q searches what was said (transcripts, AI summaries and topics — "leaking" also finds "leak"; quote a phrase for an exact match); topic filters to one AI topic tag. 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 (with its source and who referred them), text consent and recent messages, upcoming appointments, trusted status.

Customer timeline

GET/customer-timeline

?phone=8135551234&type=calls|texts|notes|appointments|reviews&q=&limit=

One customer's whole history, newest first: AI and team calls (summary, who answered), texts both ways, leads, appointments, review asks, callback requests, notes and emails — plus their pinned note, tags, contact details and linked job. q searches their history.

List leads

GET/leads

?from=&to=&status=hot|warm|cold&source=organic|angi|thumbtack|yelp|lsa|google_lsa|meta|digital_card|webchat|website_assistant|pbx&referred=true&limit=&offset=

Leads newest-first, paginated. `source=organic` = leads your AI captured on a call; `referred=true` = only referred leads. Rows include `source`, `lead_cost_cents`, `external_ref`, `referred_by`, and for card leads `card_id` and the intake answers.

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. Pictures come back as `photos` — links that work for one hour.

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, hours, average job value and the products it owns; agent; routing and answering mode; text-back; appointment reminders; Marketing (consent, referral asks, repeat-booking); 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 AI minutes vs. the plan with an end-of-month projection, extra minutes and their cost so far (50¢/min past the allowance), plus the products owned and what the business pays per month for each.

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.

List reviews

GET/reviews

?rating=1-5&responded=true|false&limit=&offset=

Synced reviews newest-first — platform, rating, author, text, and whether a response has been posted.

Review stats

GET/review-stats

Average rating, review count, monthly velocity, rating distribution, needs-response count, and reviews to the next milestone.

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 52 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 crushitengine \
  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?

The events and the everyday actions and searches on this page also exist as a Zapier app — instant triggers and Zap steps, no code required, with Zapier handling delivery retries. Connect it from Integrations → Automation 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.