Already Have an Account? Sign In ›

For AI-agent builders

Give your AI agent an iMessage channel.

Copy, paste, ship. Every snippet your agent needs to send and receive real iMessages — REST, webhooks, MCP, and LLM tool schemas. Blue-bubble delivery, ~98% open, no A2P 10DLC.

Get your API keyFull API docs

The map

Which API do I use?

Every action your agent needs and exactly what to call for it. Full reference at docs.tuco.ai.

To…UseCall
Send a message (iMessage / SMS / email)RESTPOST /api/messages
Send with attachmentsRESTPOST /api/messages (attachmentUrls[])
Get replies on demandRESTGET /api/replies
Be notified of a reply (real time)Webhookmessage.reply → your endpoint
Be notified of sent / delivered / failedWebhookmessage.sent · message.failed
Be notified of SMS fallbackWebhookmessage.fallback
Approve/act on an AI-drafted replyWebhookmessage.ai_draft → your endpoint
Check if a number is on iMessageRESTGET /api/check-availability
Drive Tuco from an LLM clientMCPnpx tuco-mcp
Let a model call it as a toolREST via toolsend_imessage → POST /api/messages
Pull a lead's reply historyRESTGET /api/replies?leadId=
Pull analytics / KPIsRESTGET /api/analytics
Find / list leadsRESTGET /api/leads
Stop future messages to a leadRESTPOST /api/messages/cancel-for-lead
Get the lines (numbers) you ownRESTGET /api/lines/by-user
Order a new line (provision a number)RESTPOST /api/line-requests

Building a CRM on Tuco?

One prompt. Paste it into your AI.

Drop this into Claude Code, Cursor, or any coding agent and it scaffolds a working Tuco integration end-to-end — API-key auth check, iMessage availability, send + SMS fallback, reply webhooks (with signature verification), and the CRM read/CRUD endpoints (replies, analytics, leads, cancel/opt-out). It also tells the AI to verify every call against the live docs, so it stays correct as the API evolves. For the full walkthrough, see the definitive guide to building an iMessage CRM.

master prompt — build a CRM on Tuco (copy-paste)
You are building a CRM / integration on top of Tuco AI's iMessage API.

BASE URL:  https://app.tuco.ai
AUTH:      every request sends header  Authorization: Bearer $TUCO_API_KEY   (key looks like tuco_sk_...)
SOURCE OF TRUTH: the live docs at https://docs.tuco.ai/api-reference and the OpenAPI spec at
  https://docs.tuco.ai/api-reference/openapi.json. This API can change — BEFORE you implement each
  step, fetch the relevant doc page and confirm the exact path, params, and response shape. If any
  call returns an unexpected 4xx or a field is missing, re-read the docs and adjust. Docs win over
  this prompt.

Build these, in order, with clean error handling + retries:

1. AUTHENTICATE — confirm the key works with one cheap authed GET (e.g. GET /api/lines or
   GET /api/replies?limit=1). 200 = good, 401 = bad key. Fail fast with a clear message.

2. CHECK iMESSAGE AVAILABILITY — before sending, GET /api/check-availability?address=+1XXXXXXXXXX
   to see if the number is on iMessage (bulk: see bulk-check-availability).

3. SEND A MESSAGE (+ FALLBACK) — POST /api/messages
   { "recipientPhone": "+1XXXXXXXXXX", "message": "…", "messageType": "imessage" }
   Address by recipientPhone, recipientEmail, or an existing leadId; omit fromLineId to round-robin.
   Fallback: set "messageType":"sms" to force SMS, or rely on Tuco's auto SMS fallback for
   non-iMessage numbers (configure the SMS route in the dashboard). Capture the 200/202 + messageId.

4. GET NOTIFIED OF REPLIES (webhook) — register once: POST /api/webhooks
   { "url": "https://your-app/webhooks/tuco",
     "events": ["message.reply","message.sent","message.failed","message.fallback"] }
   On each POST to your URL: (a) VERIFY the X-Tuco-Signature header = HMAC-SHA256(rawBody, yourWebhookSecret)
   before trusting it; (b) read the event from X-Tuco-Event / body.event; (c) for message.reply the reply
   text is the top-level "message" string, the contact is "leadId"/"phone", and "optedOut" tells you if
   they opted out. Prefer polling? GET /api/replies?recipientPhone=…&limit=N instead.

5. RESPOND TO A REPLY — POST /api/messages again to the same recipientPhone or leadId with your answer.
   Threads are keyed by contact.

CRM EXTRAS (confirm each against the docs first):
- Conversation history / replies:  GET /api/replies?leadId=…   (or ?recipientPhone=…)
- Analytics / KPIs:                 GET /api/analytics
- Leads:                            GET/POST /api/leads  (create, list, get); to find a lead by
                                    phone/email, check the leads docs for the search/filter params.
- Stop / cancel future messages (opt-out / DNC):  POST /api/messages/cancel-for-lead
- Lines (numbers) you own:          GET /api/lines/by-user
- Order / provision a new line:     POST /api/line-requests   (check status: GET /api/line-requests/:id)
- Reactions / typing / attachments: /api/messages/react, /api/messages/typing,
                                    send-with-attachments, upload-attachment

Deliverable: a typed API client + a webhook receiver (with signature verification) + the read/CRUD
endpoints above — each verified against https://docs.tuco.ai before you ship.

Step 1

Get your API key

Create a workspace key in the API Keys tab. It looks like tuco_sk_… and authenticates both the REST API and the MCP server. Keep it server-side.

shell
export TUCO_API_KEY="tuco_sk_xxxxxxxxxxxxx"

Step 2

Send a message

One POST to /api/messages. Address by recipientPhone (E.164), recipientEmail, or an existing leadId. Omit fromLineId and Tuco round-robins your active lines.

curl
curl https://app.tuco.ai/api/messages \
  -H "Authorization: Bearer tuco_sk_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientPhone": "+12025551234",
    "message": "Hey {{firstName}} — following up on your demo request. Free this week?",
    "messageType": "imessage"
  }'
node
// messageType: "imessage" | "sms" | "email"  (defaults to imessage)
const res = await fetch("https://app.tuco.ai/api/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.TUCO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    recipientPhone: "+12025551234", // or recipientEmail, or leadId
    message: "Following up on your demo request — free this week?",
    messageType: "imessage",
  }),
});
const data = await res.json();

Step 3

Receive replies

Push (recommended): point a webhook at your endpoint and Tuco POSTs each event — message.reply, message.sent, message.failed, message.fallback, message.ai_draft. Pull: poll /api/replies.

webhook payload your endpoint receives
POST  https://your-app.com/webhooks/tuco
Headers:
  X-Tuco-Event:      message.reply
  X-Tuco-Signature:  <HMAC-SHA256 of the raw body, keyed with your webhook secret>
  X-Tuco-Timestamp:  2026-08-22T18:22:41.000Z

Body (message.reply):
{
  "event": "message.reply",
  "timestamp": "2026-08-22T18:22:41.000Z",
  "workspaceId": "org_abc123",
  "leadId": "69b2c41d352a78479d2c623b",
  "lead": { "firstName": "Jane", "lastName": "Doe", "phone": "+15551230001" },
  "message": "Yes! Thursday 2pm works.",      // reply text (a string, for message.reply)
  "firstName": "Jane",                          // flat convenience fields
  "phone": "+15551230001",
  "fromLineId": "6a82fbf0586005e0ca405c37",
  "repliedAtUtc": "2026-08-22T18:22:41.000Z",
  "parentMessages": [ { "text": "Hey Jane — following up…", "direction": "out" } ],
  "attachmentDownloadUrls": [],                 // signed URLs when they send a photo/file
  "optedOut": false                             // true if the reply auto-triggered opt-out
}

Every delivery is signed. Verify the X-Tuco-Signature header (HMAC-SHA256 of the raw body, keyed with your webhook secret) before you trust the payload:

verify signature (node)
import crypto from "crypto";

// Verify the webhook really came from Tuco BEFORE you trust it.
// Use the RAW request body (not the parsed JSON) to compute the HMAC.
function verifyTucoWebhook(rawBody, headers) {
  const sig = headers["x-tuco-signature"];
  const expected = crypto
    .createHmac("sha256", process.env.TUCO_WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");
  return !!sig && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
or poll instead — curl
curl "https://app.tuco.ai/api/replies?recipientPhone=%2B12025551234&limit=10" \
  -H "Authorization: Bearer tuco_sk_xxxxxxxxxxxxx"

Step 4

Connect via MCP (Claude, Cursor, Windsurf…)

Prefer a model-side client to drive Tuco in natural language? Drop this into your MCP config. No glue code — the tuco-mcp server exposes send, reply, and analytics tools.

mcp config (claude_desktop_config.json / .cursor/mcp.json)
{
  "mcpServers": {
    "tuco": {
      "command": "npx",
      "args": ["tuco-mcp"],
      "env": {
        "TUCO_API_KEY": "tuco_sk_xxxxxxxxxxxxx"
      }
    }
  }
}

Step 5

Add the tool to your LLM

Building your own agent loop? Register send_imessage as a tool, then route the call to /api/messages with the handler below. Both tool-calling formats:

OpenAI function
{
  "type": "function",
  "function": {
    "name": "send_imessage",
    "description": "Send an iMessage to a contact via Tuco. Use for follow-ups, reminders, and replies.",
    "parameters": {
      "type": "object",
      "properties": {
        "recipientPhone": { "type": "string", "description": "E.164 phone, e.g. +12025551234" },
        "message": { "type": "string", "description": "The text to send. Keep it short and human." }
      },
      "required": ["recipientPhone", "message"]
    }
  }
}
Anthropic tool
{
  "name": "send_imessage",
  "description": "Send an iMessage to a contact via Tuco. Use for follow-ups, reminders, and replies.",
  "input_schema": {
    "type": "object",
    "properties": {
      "recipientPhone": { "type": "string", "description": "E.164 phone, e.g. +12025551234" },
      "message": { "type": "string", "description": "The text to send. Keep it short and human." }
    },
    "required": ["recipientPhone", "message"]
  }
}
tool handler (node)
// Wire the tool call to Tuco. Return the result to your model.
async function send_imessage({ recipientPhone, message }) {
  const res = await fetch("https://app.tuco.ai/api/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.TUCO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ recipientPhone, message, messageType: "imessage" }),
  });
  return res.json();
}

Step 6

Paste this into your system prompt

Teach the model how to behave on the channel. Human, opt-in, no links before a reply — the habits that keep your line healthy.

system prompt
You can send and receive iMessages through Tuco by calling the
send_imessage tool. To reach a contact, pass their phone in E.164 format
(e.g. +12025551234) and your message.

Rules:
- Write like a real person texting, not a marketing blast. Short and human.
- Personalize with the contact's name when you have it.
- Never send a link until the contact has replied at least once.
- Only message people who opted in. Respect any "stop"/opt-out immediately.
- When a contact replies, you'll receive their message — respond promptly.

FAQ

Builder questions, answered.

How does an AI agent authenticate?

Every request uses a workspace API key as a bearer token: Authorization: Bearer tuco_sk_… Create one in the API Keys tab of the Tuco dashboard. The same key authenticates the REST API and the MCP server.

Do I need A2P 10DLC to send from my agent?

No. iMessage routes through Apple's network, not the US carrier SMS system, so A2P 10DLC registration and per-message carrier surcharges don't apply. If a recipient isn't on iMessage, you can fall back to SMS by setting messageType to "sms".

How does my agent receive replies?

Two ways. Push: register a webhook and Tuco POSTs an event (message.reply, message.sent, message.failed, message.fallback, message.ai_draft) to your endpoint in real time. Pull: GET /api/replies?recipientPhone=… or ?leadId=… on a schedule. Most agents use webhooks.

Should I use the REST API or the MCP server?

Use the MCP server when a model-side client (Claude Desktop, Cursor, Windsurf, or your own MCP-capable agent) should drive Tuco with natural language — it exposes ready tools with no glue code. Use the REST API when you're building your own backend or wiring a specific LLM tool/function call. They share the same authenticated API.

Does it work with OpenAI and Anthropic tool calling?

Yes. Drop the send_imessage schema into your tools/functions list (both formats are on this page), then route the tool call to POST /api/messages with the handler shown above. It works with any model that supports tool use.

Where are the full API docs?

The complete reference — every endpoint, webhook event, and the MCP tool list — lives at docs.tuco.ai. This page is the fast copy-paste starting point.

Ship your agent an iMessage channel.

Grab a key, paste the snippets, and your agent is texting on a live line — new lines activate in ~24-48 hours. See the AI agents use case or Agentic overview.

Get your API keyView pricing
Get StartedStarter self-serve, Growth demos