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

# Drip (bulk shock-absorber)

> POST /api/drip — same body as /api/messages, never 429s a bulk blast, fast-acks 202, and drips sends out safely per line. Built for HubSpot/CRM parallel enrollment fan-out.

## Overview

`POST /api/drip` is a per-request intake endpoint that accepts the **exact same
body as [`POST /api/messages`](/api-reference/messages)** but is built for **bulk,
bursty** traffic — HubSpot and other CRMs fan enrollments out in parallel and
retry any `429`/timeout for up to 3 days, which turns a rate limit into a retry
storm and duplicate sends.

`/api/drip` solves that:

* **Never rate-limited.** It is exempt from the 60/min send limiter, so a blast is
  never rejected with `429` (an infra backstop exists only for runaway abuse — see
  [Limits](#limits)).
* **Fast-acks `202`** in well under 2 seconds.
* **Drips safely.** Every accepted message is inserted as a **jittered scheduled
  send**, then the existing worker + per-line **ban protector** pace the real sends
  so a line is never pushed past its safe cadence (which is what protects your
  iMessage lines from Apple).

<Info>
  **To switch a HubSpot/CRM integration from `/api/messages` to `/api/drip`, change
  ONLY the URL you POST to.** The request body, auth, and per-request contract are
  identical. Use `/api/drip` for bulk enrollment blasts; keep `/api/messages` for
  low-volume, send-now traffic where you want the immediate delivery status back.
</Info>

***

## When to use which

|                 | `POST /api/messages`                        | `POST /api/drip`                               |
| --------------- | ------------------------------------------- | ---------------------------------------------- |
| Best for        | Interactive / low-volume send-now           | Bulk, bursty CRM enrollment fan-out            |
| Rate limit      | 60 req/min per workspace (`429` past that)  | **None** (infra backstop only)                 |
| On success      | May send synchronously; returns real status | Always scheduled; returns `202 queued`         |
| Delivery pacing | Immediate (subject to line caps)            | Jittered + ban-protector paced per line        |
| Response        | `201` with the message object               | `202 { status, correlationId, scheduledDate }` |

***

## Request

`POST https://app.tuco.ai/api/drip`

**Auth:** `Authorization: Bearer <YOUR_API_KEY>` (same key as `/api/messages`; a
Clerk session also works). **Method:** `POST` only.

**Body:** the same per-message shape as [`POST /api/messages`](/api-reference/messages#request).
The delivery/routing fields are forwarded verbatim: `message`, `messageType`,
`recipientPhone` / `recipientEmail` / `recipientName`, `leadId`, `hsContactId` /
`hsPortalId`, `ghlContactId` / `ghlLocationId`, `pdCompanyId` / `pdPersonId` /
`pdLeadId`, `attachmentUrls` / `attachmentNames`, `sendFallbackSmsOnFailed` /
`smsFallbackEnabled` / `smsFallbackMessage` / `forceFallback`, `correlationId` /
`traceId`, and the send-window settings `timezone` / `sendWindowStart` /
`sendWindowEnd` / `allowedDaysOfWeek` (honored — a message is clamped into your
window before it drips).

<Note>
  Campaign-orchestration fields are **not** applied by `/api/drip` (it is a
  per-message shock-absorber, not a campaign runner): `batchId`, `stepIndex`,
  `eligibleLineIds`, `stickinessPolicy`, the failover knobs, `isAgentDriven`, and
  `needsAiPersonalization` are ignored. Use campaigns for those.
</Note>

Two fields are managed **for** you and cannot be set:

* **`scheduledDate`** — always overwritten with a jittered future time (min now
  * 30s, spread over \~5 min) so the send is queued, never fired at intake. Any
    `scheduledDate` you pass is ignored.
* **Line selection** — if you omit `fromLineId`, a line is chosen by the same
  workspace round-robin `/api/messages` uses (a persisted, workspace-wide rotation
  so no single line is hammered). An explicit `fromLineId` is honored and validated
  to your workspace.

<ParamField header="Idempotency-Key" type="string">
  Optional. HubSpot retries **will** resend — pass a stable per-message
  `Idempotency-Key` and a retry returns the original `202` instead of queueing a
  second message. Even without it, an atomic 60-minute dedup guard on
  `(workspace, recipient, body)` collapses accidental duplicates.
</ParamField>

### Example

```bash theme={null}
curl -X POST https://app.tuco.ai/api/drip \
  -H "Authorization: Bearer $TUCO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: hs-enroll-8471-step2" \
  -d '{
    "message": "Hi Jane — following up on your quote.",
    "recipientPhone": "+14155551234",
    "hsContactId": "8471",
    "hsPortalId": "1234567"
  }'
```

***

## Response

`202 Accepted` — fast-ack. The request is buffered and the message is created a
moment later, then sent still later (paced per line). See below.

```json theme={null}
{
  "status": "queued",
  "correlationId": "drip_1783787897123_a1b2c3",
  "scheduledDate": "2026-07-11T18:32:07.412Z",
  "jitterMs": 127412
}
```

<ResponseField name="status" type="string">Always `"queued"`.</ResponseField>
<ResponseField name="correlationId" type="string">Tracking id for this request (echoed from your `correlationId` / `X-Correlation-Id` if you sent one). The message itself is created shortly after the ack, so no `messageId` is returned here — grep this id in your webhook payloads to correlate.</ResponseField>
<ResponseField name="scheduledDate" type="string">ISO 8601 — the jittered future time the message becomes eligible to send. If you set a send window it is clamped into it; the ban protector may push it later still at drain time if the line is at its daily cap.</ResponseField>
<ResponseField name="jitterMs" type="number">The jitter applied over the +30s floor, for observability.</ResponseField>

A duplicate (same `Idempotency-Key`) returns `200` with the original ack — no
second message. Same-content duplicates `(workspace, recipient, body)` within 60
minutes are also collapsed to one message downstream.

### What happens next

The intake is **buffered** (Redis): it accepts your blast instantly and never
touches the database on the request path, so it absorbs large parallel bursts. A
paced worker then drains the buffer at a database-safe rate and creates each
message as a **scheduled** send (`source: "hubspot-drip"`), visible in **Unibox →
Scheduled**. At each message's scheduled time the per-line **ban protector** allows
it only if the line is under its safe cadence (150/day, \~20–50 new
conversations/day, \~3–7 new/hr) and otherwise **reschedules** it (hourly cap →
next hour, daily cap → next workspace morning). Two independent throttles —
buffer→database, then database→Apple — so a burst in never becomes a burst out.
Nothing is dropped. Delivery status flows through the same
[message webhooks](/api-reference/message-webhooks) as `/api/messages`.

***

## Limits

`/api/drip` is **not** subject to the 60/min send limit and will not `429` a normal
blast. The only limit is a high **infra backstop** on the buffered intake: \~1,000
requests/sec sustained per workspace (a 10,000-request burst bucket). It trips
solely on runaway/abuse; if you ever hit it you receive `429` + `Retry-After` and
can retry safely. Because the intake is buffered, database write pressure is
governed by the paced drainer, not by how fast you post.

| Limit            | Value                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------- |
| Body             | Single message per request (same shape as `/api/messages`)                                |
| Rate limit       | **None** — exempt from the 60/min send limiter (infra backstop only)                      |
| Infra backstop   | \~1,000 req/sec sustained / 10,000 burst per workspace → `429` + `Retry-After` on runaway |
| Real send pacing | Per-line ban protector: 150/day, \~20–50 new conversations/day, \~3–7 new/hr              |
| Ack              | `202 { status, correlationId, scheduledDate }` in \< 2s                                   |
| Auth             | API key (Bearer) or Clerk                                                                 |
| Dedup            | `Idempotency-Key`, plus atomic `(workspace, recipient, body)` 60-min guard                |

See [Errors & rate limits](/api-reference/errors) for the full table.
