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

# Errors & Status Codes

> HTTP status codes, error formats, and what to do when things go wrong. Part of the Tuco AI iMessage API reference — bearer-token auth, JSON responses, examples.

## Error format

All error responses return a JSON body with an `error` field. Some include
a machine-readable `code` for programmatic handling.

```json theme={null}
{
  "error": "Human-readable description of the problem",
  "code": "MACHINE_READABLE_CODE"
}
```

***

## HTTP status codes

| Code    | Meaning                                                |
| ------- | ------------------------------------------------------ |
| **200** | Request succeeded                                      |
| **201** | Resource created (e.g. message, lead)                  |
| **400** | Validation error — check the `error` field for details |
| **401** | Authentication failed — invalid or missing API key     |
| **402** | Workspace is read-only (e.g. subscription past due)    |
| **404** | Resource not found (e.g. lead, line, campaign)         |
| **429** | Rate limit exceeded — wait and retry                   |
| **500** | Internal server error — retry or contact support       |

***

## Send Message errors (`POST /api/messages`)

These are the specific errors you may encounter when sending a message.

<Note>
  Pre-send checks like line limits, time windows, device gaps, and contact gaps
  **never** return errors. The message is accepted with `201` and the `status`
  field indicates the outcome (`pending`, `scheduled`, etc.).
</Note>

<AccordionGroup>
  <Accordion title="400 — Message and attachments both empty">
    Neither `message` nor `attachmentUrls` was provided (or both were empty).

    ```json theme={null}
    { "error": "Message and attachmentUrls cannot both be empty" }
    ```

    **Fix:** Include at least one of: a non-empty `message` string, or an `attachmentUrls` array with up to 2 URLs.
  </Accordion>

  <Accordion title="400 — Invalid messageType">
    The `messageType` value is not one of the accepted values.

    ```json theme={null}
    { "error": "Invalid messageType. Must be email, sms, or imessage" }
    ```

    **Fix:** Use `"imessage"`, `"sms"`, or `"email"`. Or omit it entirely to
    default to `"imessage"`.
  </Accordion>

  <Accordion title="400 — No active lines in workspace">
    No `fromLineId` was provided and the workspace has no active lines to
    round-robin from.

    ```json theme={null}
    { "error": "No active lines in workspace..." }
    ```

    **Fix:** Provision at least one line in your workspace, or pass `fromLineId`
    explicitly.
  </Accordion>

  <Accordion title="400 — Missing recipient">
    No `recipientPhone`, `recipientEmail`, or `leadId` was provided, and the
    resolved lead (if any) has no contact details.

    ```json theme={null}
    { "error": "Either recipientEmail or recipientPhone is required..." }
    ```

    **Fix:** Provide at least one of `recipientPhone`, `recipientEmail`, or a
    `leadId` that has stored contact details.
  </Accordion>

  <Accordion title="400 — Fallback not configured">
    `sendFallbackSmsOnFailed` was set to `true` but the workspace doesn't have
    Twilio fallback configured.

    ```json theme={null}
    { "error": "...", "code": "FALLBACK_NOT_CONFIGURED" }
    ```

    **Fix:** Configure Twilio on your workspace via
    `PATCH /api/workspace/fallback-config`, or set `sendFallbackSmsOnFailed`
    to `false`.
  </Accordion>

  <Accordion title="401 — Unauthorized">
    The API key is missing, invalid, or expired.

    ```json theme={null}
    { "error": "Unauthorized" }
    ```

    **Fix:** Check your `Authorization: Bearer <key>` header. Generate a new
    key from your workspace settings if needed.
  </Accordion>

  <Accordion title="402 — READ_ONLY (subscription past due)">
    The workspace subscription is past due and write operations are blocked.

    ```json theme={null}
    { "error": "READ_ONLY", "code": "READ_ONLY", "reason": "past_due" }
    ```

    **Fix:** Update your billing in the Tuco dashboard.
  </Accordion>

  <Accordion title="404 — Lead not found">
    A `leadId` was provided but no matching lead exists in the workspace.

    ```json theme={null}
    { "error": "Lead not found or access denied" }
    ```

    **Fix:** Verify the `leadId` exists and belongs to the same workspace as
    the API key.
  </Accordion>
</AccordionGroup>

***

## Status vs error — key distinction

<Warning>
  A `201` response with `status: "pending"` or `status: "scheduled"` is **not**
  an error. It means the message was accepted and Tuco will send it when
  conditions are met (time window, line limits, device gap, etc.).

  Only `4xx` and `5xx` responses indicate actual errors.
</Warning>

| You received          | What it means                                 | Action                             |
| --------------------- | --------------------------------------------- | ---------------------------------- |
| `201` + `"pending"`   | Message accepted, sending soon                | No action needed                   |
| `201` + `"scheduled"` | Message accepted, will send at scheduled time | No action needed                   |
| `201` + `"sent"`      | Message sent immediately                      | No action needed                   |
| `201` + `"fallback"`  | Recipient has no iMessage, fallback may fire  | Optional: send via another channel |
| `400`                 | Validation error                              | Fix the request and retry          |
| `401`                 | Auth error                                    | Check API key                      |
| `402`                 | Billing issue                                 | Update payment                     |
| `404`                 | Resource not found                            | Check IDs                          |

***

## Rate limits

Tuco applies per-workspace rate limits to protect the platform. When you hit
a limit:

* You receive a `429` response.
* The `Retry-After` header tells you how many seconds to wait before retrying.
* No data is modified — safe to retry.

| Endpoint group                                                                                  | API rate limit                             | Daily cap                                               |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------- |
| **Send message** (`/api/messages`)                                                              | 60 req/min                                 | Per-line (see `GET /api/lines/by-user` for your limits) |
| **Drip (bulk shock-absorber)** (`/api/drip`)                                                    | **Not rate-limited** (infra backstop only) | Per-line ban protector (paced automatically)            |
| **Check iMessage availability** (`/api/check-availability-rr`, `/api/leads/check-availability`) | 200 req/min                                | **70 checks/day per line**                              |
| **Get replies** (`/api/replies`)                                                                | 120 req/min                                | —                                                       |
| **Create campaign** (`/api/campaigns`)                                                          | 200 req/min                                | —                                                       |
| **Campaign add lead** (`/api/campaigns/{id}/leads`)                                             | 200 req/min                                | —                                                       |
| **Unibox reply** (`/api/unibox/send-reply`)                                                     | 30 req/min                                 | —                                                       |
| **Fallback SMS** (`/api/fallback-sms`)                                                          | 200 req/min                                | —                                                       |

All limits are per workspace (organization), regardless of how many API keys or users are calling the endpoint.

<Note>
  **`POST /api/drip` is exempt from the 60/min send limit** and never returns `429`
  for a normal blast — that is the whole point of the endpoint (a `429` would trigger
  your CRM's retry storm and duplicate sends). It applies only a very high **infra
  backstop** on the buffered intake (\~1,000 req/sec sustained / 10,000 burst per workspace) that
  trips solely on runaway/abuse; if you ever hit it you get `429` + `Retry-After` and
  can safely retry. Real send pacing (150/day, \~3–7 new conversations/hr per line) is
  handled automatically by the per-line ban protector at send time, not at intake. See
  [Drip](/api-reference/drip).
</Note>

<Note>
  **Availability checks have two limits:**

  1. **200 req/min** API rate limit (token bucket, per workspace)
  2. **70 checks/day per line** hard cap (each check hits a real device). With 3 lines = 210/day total.

  When the **daily cap** is exhausted (or no line is healthy), the check can't be completed, so
  it returns `200` with `{ "available": false, "verified": false, "reason": "daily_quota_exhausted" }`
  (or `"reason": "no_healthy_line"`) — **not** a `429`. This keeps CRM workflow steps from failing
  on an un-checkable contact. Use `verified: false` to tell "couldn't check" apart from a confirmed
  not-on-iMessage result (`available: false` with no `verified` flag). Only the **per-minute rate
  limit** (#1 above) returns `429` with `Retry-After`.

  Availability checks do **not** consume your daily send quota — they use separate counters.
</Note>

<Tip>
  **For API integrators:** Skip availability checking entirely. Just call `POST /api/messages`
  and listen for the `message.fallback` webhook to trigger your SMS/WhatsApp fallback.
  This avoids burning your 70/day availability quota. See the
  [check-availability-rr endpoint](/api-reference/endpoint/check-availability-rr#recommended-pattern-send--fallback-webhook)
  for the recommended pattern.
</Tip>

<Tip>
  For high-volume sends, use campaigns instead of individual API calls. Campaigns
  handle pacing, line rotation, and retry logic automatically — and are not subject
  to per-request rate limits.
</Tip>
