Skip to main content
POST
/
api
/
messages
Send Message
curl --request POST \
  --url https://app.tuco.ai/api/messages \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "message": "<string>",
  "attachmentUrls": [
    "<string>"
  ],
  "recipientPhone": "<string>",
  "recipientEmail": "<string>",
  "messageType": "<string>",
  "fromLineId": "<string>",
  "leadId": "<string>",
  "recipientName": "<string>"
}
'
{
  "success": true,
  "status": "<string>",
  "message": {
    "_id": "<string>",
    "message": "<string>",
    "messageType": "<string>",
    "status": "<string>",
    "fromLineId": "<string>",
    "recipientPhone": "<string>",
    "recipientEmail": "<string>",
    "leadId": "<string>",
    "scheduledDate": "<string>",
    "sentAt": "<string>",
    "deliveredAt": "<string>",
    "createdAt": "<string>"
  }
}

Documentation Index

Fetch the complete documentation index at: https://docs.tuco.ai/llms.txt

Use this file to discover all available pages before exploring further.

This is the primary endpoint for automation tools like GetSales, n8n, Make, or your own backend. One call = one message. Tuco handles retries, delivery verification, and fallback internally.

Authentication

Pass your workspace API key as a Bearer token, or use a Clerk session token.
Authorization: Bearer tuco_sk_xxxxxxxxxxxxx

Request body

message
string
The text content to send. Required unless attachmentUrls is provided (you can send attachments only, or message + attachments). Personalization placeholders like {{firstName}} are resolved from the lead at send time.
attachmentUrls
string[]
Optional array of URLs (max 2, max 25 MB each). When provided, message can be empty. Supported formats: images (PNG, JPG, GIF, WebP, HEIC), video (MP4, MOV), audio (CAF), and PDF. Supported sources: any public URL, UploadThing/Blob URLs that belong to your workspace.
Sending the same attachment URL repeatedly (e.g. a promo video in an automation)? Tuco caches attachments per line — the second send skips the download and upload, making it significantly faster.
recipientPhone
string
Phone number in E.164 format (e.g. "+12025551234"). Required unless recipientEmail or leadId is provided.
recipientEmail
string
Email address for email or iMessage (Apple ID). Required unless recipientPhone or leadId is provided.
messageType
string
default:"imessage"
Channel to use. When omitted, defaults to "imessage".
ValueChannelAddresses tried (from lead)
"imessage"iMessage via device relayphone → altPhone1-3 → email → altEmail1-3
"sms"SMS via Twiliophone → altPhone1-3 only
"email"Emailemail → altEmail1-3 only
fromLineId
string
Tuco line ID to send from. Optional — when omitted, Tuco round-robins across your workspace’s active lines automatically.
leadId
string
Send to an existing lead using their stored contact details. When provided together with recipientPhone/recipientEmail, the body recipient is used as the send-to address while the lead is used for linking.
recipientName
string
Display name for the recipient. Derived from the lead when omitted.
Stored on the lead (created or found). The worker tries these in priority order when the primary address fails iMessage availability.
altPhone1
string
Alternate phone number 1
altPhone2
string
Alternate phone number 2
altPhone3
string
Alternate phone number 3
altEmail1
string
Alternate email 1
altEmail2
string
Alternate email 2
altEmail3
string
Alternate email 3
Control when the message is sent. All fields are optional. When omitted, the message sends immediately (subject to line limits and device gaps).
scheduledDate
string
ISO 8601 timestamp to send in the future (e.g. "2025-10-15T14:30:00Z").
timezone
string
IANA timezone for the send window (e.g. "America/New_York"). Required if using sendWindowStart/sendWindowEnd.
sendWindowStart
string
Earliest time to send, HH:mm format (e.g. "09:00").
sendWindowEnd
string
Latest time to send, HH:mm format (e.g. "17:00").
allowedDaysOfWeek
number[]
Days when sending is allowed. 0 = Sunday, 6 = Saturday. Example: [1,2,3,4,5] for weekdays only.
sendFallbackSmsOnFailed
boolean
default:"false"
When true, if the message ends in failed status (technical error after retries), Tuco sends a fallback SMS via your configured Twilio number. Returns 400 with code: "FALLBACK_NOT_CONFIGURED" if enabled but no fallback is set up on your workspace.
batchId
string
Free-form string to group related messages for reporting.

Examples

Create or import a lead first using POST /api/leads, then send using leadId as shown below.
curl -X POST "https://app.tuco.ai/api/messages" \
  -H "Authorization: Bearer tuco_sk_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Hi, this is a test from Tuco.",
    "leadId": "667f1f77bcf86cd799439012"
  }'

Response

The API always returns 201 when the message is successfully created. The status field tells you what happens next.
Pre-send checks are never errors. Line limits, time windows, device gaps, and contact gaps cause status: "pending" or "scheduled" — the message is accepted and will send when conditions are met.

Success (201 Created or 200 OK when duplicate lead used)

{
  "success": true,
  "status": "sent",
  "message": {
    "_id": "69b344bc6dae1d942ece4d0e",
    "message": "Here's your document.",
    "messageType": "imessage",
    "status": "sent",
    "fromLineId": "69ab427ac1feb77d7f46462e",
    "recipientPhone": "+919042956129",
    "recipientEmail": "goforbg@icloud.com",
    "recipientName": "BG Real",
    "leadId": "69b2c41d352a78479d2c623b",
    "workspaceId": "org_3AZs4H8UsfFxVFONr6H4K75okaG",
    "createdAt": "2026-03-12T22:57:00.856Z",
    "updatedAt": "2026-03-12T22:57:02.892Z",
    "sentAt": "2026-03-12T22:57:02.892Z",
    "externalMessageId": "5CA93911-F0FE-4D1F-8D8C-1E3495A124F6"
  },
  "leadId": "69b2c41d352a78479d2c623b",
  "ghlContactId": "aMUQn0u0Z7cw0NQ7tJ5R",
  "ghlLocationId": "eL7DD22BdZ0rismu7qCA",
  "hsPortalId": null,
  "hsContactId": null
}
When the recipient was matched to an existing lead (duplicate), the API returns 200 with duplicateOnly: true, existingLeadIds, and leadIds so your automation can continue without treating it as an error.
success
boolean
true when the message was created.
status
string
Current status of the message. See the status table below.
message
object
The full message document. Key fields:

Status values

StatusMeaningIs it an error?
"sent"Message sent immediately (sync path)No
"pending"Created, worker will process. Line limits / time window / device gap may delay it.No — it will send when checks pass
"scheduled"Will send at scheduledDate, or rescheduled due to device gapNo
"fallback"Recipient has no iMessage; fallback SMS sent if configuredNo (business rule)
"failed"All retries exhausted or availability API errorYes (technical)
"pending" and "scheduled" mean the message is accepted and queued. The worker sends it when:
  • Time window is satisfied (inside sendWindowStartsendWindowEnd)
  • Allowed day is satisfied (today is in allowedDaysOfWeek)
  • Line limits reset (daily total or new conversations limit)
  • Device gap is met (default 30s between sends from same device)
  • Contact gap is met (default 45s between first messages to different contacts on same line)
None of these cause an error response.

Errors

Errors are returned only for validation failures — never for pre-send checks.
StatusWhenExample
400Message and attachments both empty{ "error": "Message and attachmentUrls cannot both be empty" }
400Invalid messageType{ "error": "Invalid messageType. Must be email, sms, or imessage" }
400No active lines + no fromLineId{ "error": "No active lines in workspace..." }
400No recipient anywhere{ "error": "Either recipientEmail or recipientPhone is required..." }
400sendFallbackSmsOnFailed: true but fallback not configured{ "error": "...", "code": "FALLBACK_NOT_CONFIGURED" }
401Invalid or missing API key{ "error": "Unauthorized" }
402Subscription past due (workspace read-only){ "error": "READ_ONLY", "code": "READ_ONLY", "reason": "past_due" }
404leadId provided but not found{ "error": "Lead not found or access denied" }

What happens after the API call

1

Message created

Tuco inserts a record in the messages collection with status pending (or scheduled if scheduledDate is provided).
2

Pre-send checks (worker)

The worker checks line limits, time window, device gap, and contact gap. If any fail, the message stays queued and is retried later — not failed.
3

Availability check

If the workspace has a line with Private API, Tuco checks whether the recipient supports iMessage. If not → status becomes fallback and fallback SMS fires (when configured).
4

Send

For individual messages: up to 5 send attempts with delivery verification between each (polling for up to 90 seconds). For campaigns: 1 attempt per line.
5

Outcome

sent → appears in Unibox + webhook fires. failed → error recorded + optional fallback SMS. delivered → confirmed later via device callbacks.

Lead resolution

When you send without a leadId, Tuco resolves the recipient automatically:
If an existing lead in your workspace matches the recipientPhone or recipientEmail, the message is linked to that lead. Any altPhone/altEmail fields you pass will update the lead (merge, not overwrite).
If no matching lead exists, Tuco creates one under a list called “Quick Sends” (created automatically if it doesn’t exist). Alt contact fields are stored on the new lead.
The lead must exist in your workspace. The message is linked to it. recipientPhone/recipientEmail from the body override the lead’s stored contact for this specific send.

Alt contact fields

When you pass altPhone1altPhone3 or altEmail1altEmail3, they are stored on the lead (not the message). The worker uses them as fallback addresses when the primary address fails the iMessage availability check. Priority order for messageType: "imessage":
phone → altPhone1 → altPhone2 → altPhone3 → email → altEmail1 → altEmail2 → altEmail3
Alt fields are merged onto the lead. If a lead already has altPhone1 set and you send a new message without altPhone1, the existing value is preserved. Only explicitly provided fields are overwritten.

Fallback SMS on failure

By default, when a message fails (technical error), no SMS fallback is sent. To enable:
  1. Configure Twilio on your workspace (GET /api/workspace/fallback-config).
  2. Set sendFallbackSmsOnFailed: true in the request body (per-message) or on the workspace (applies to all messages).
If you set sendFallbackSmsOnFailed: true but fallback is not configured, the API returns 400 with code: "FALLBACK_NOT_CONFIGURED".
ScenarioFallback SMS fires?
Status = fallback (no iMessage), Twilio configuredYes — always
Status = failed, sendFallbackSmsOnFailed: true, Twilio configuredYes
Status = failed, sendFallbackSmsOnFailed: false (default)No
Status = failed, flag is true but no Twilio400 error at API call time
Fallback SMS requires recipientPhone on the message. Email-only messages will not trigger fallback SMS.