> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ibubbl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build with AI

> Give your coding agent the iBubbl docs, the OpenAPI spec and a system prompt with the rules that matter.

Coding agents write better iBubbl integrations when they read the docs instead of guessing. Everything on this site is available in forms an agent can read, and the system prompt below gives it the rules that are easy to get wrong.

## Docs for agents

| Resource | URL | Use it for |
| - | - | - |
| `llms.txt` | [`https://docs.ibubbl.com/llms.txt`](https://docs.ibubbl.com/llms.txt) | An index of every page, with a one-line summary and a link to its Markdown. |
| `llms-full.txt` | [`https://docs.ibubbl.com/llms-full.txt`](https://docs.ibubbl.com/llms-full.txt) | All of these docs in one plain-text file, to paste or attach as context. |
| Markdown pages | Add `.md` to any page URL, for example [`https://docs.ibubbl.com/api-reference/messages/send.md`](https://docs.ibubbl.com/api-reference/messages/send.md) | One page as clean Markdown. |
| OpenAPI spec | [`https://docs.ibubbl.com/openapi/public.json`](https://docs.ibubbl.com/openapi/public.json) | The machine-readable `/v1` contract: generate a client, or import it into Postman or Insomnia ([how](https://docs.ibubbl.com/openapi.md)). |
| Docs MCP server | `https://docs.ibubbl.com/mcp` | Lets an agent search and read these docs while it works. |

The menu at the top of each page can also copy the page as Markdown, open it in ChatGPT or Claude, or connect the MCP server to Cursor or VS Code.

### Connect the docs MCP server

The MCP server searches and reads these docs. It can't call the iBubbl API or send messages, and it doesn't need your API key.

```bash Claude Code theme={null}
claude mcp add --transport http ibubbl-docs https://docs.ibubbl.com/mcp
```

```json Cursor theme={null}
{
  "mcpServers": {
    "ibubbl-docs": {
      "url": "https://docs.ibubbl.com/mcp"
    }
  }
}
```

For Cursor, add the entry to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for every project.

## System prompt for coding agents

Copy this into your agent's instructions (for example `CLAUDE.md`, `AGENTS.md` or `.cursor/rules`) before asking it to build an iBubbl integration. Each rule prevents a mistake that sends duplicate messages, messages people who opted out, or creates a reply loop.

```text theme={null}
You are building an integration with the iBubbl API, which sends iMessages
(with a fallback to SMS) from a line that iBubbl runs for the customer.
Follow these rules exactly.

REFERENCE
- Base URL: https://api.ibubbl.com. Every endpoint is under /v1.
- Docs: https://docs.ibubbl.com. Full text: https://docs.ibubbl.com/llms-full.txt.
  OpenAPI: https://docs.ibubbl.com/openapi/public.json. Read them before using
  an endpoint or field; never guess names, fields or values.
- Auth: send "Authorization: Bearer <key>". Read the key from the
  IBUBBL_API_KEY environment variable. Never hard-code, log or print it, and
  never put it in browser or mobile code.
- Every request is live. There is no sandbox or test mode. While testing,
  send only to the developer's own phone number.

SENDING (POST /v1/messages)
- Exactly one of: to (E.164, e.g. +14155551234), contact_id, chat_guid,
  participant_phones. Plus idempotency_key (required) and body (required
  unless attachments are sent; see ATTACHMENTS).
- Generate ONE idempotency_key (a UUIDv4) per logical message, before the
  first attempt, and store it with the message. Reuse the same key and the
  identical payload on every retry. Never generate the key inside a retry
  loop. A new key with the same body sends a second, real message: iBubbl
  never deduplicates by content.
- After a timeout, resend with the same key and body: you get the original
  message back if it was accepted, so the contact gets exactly one. Exception:
  never auto-retry a timed-out participant_phones send (it can create a second
  group); wait for its message.sent webhook first.
- 202 means accepted, not delivered. Track the outcome with webhooks or
  GET /v1/messages/{id}. A message outside the contact's sending hours stays
  "pending" with held_reason and held_until set; that is normal, not an error.
- The request has no field for the sending line. Unknown fields such as from
  or line_id are ignored without an error; each contact sticks to one line.
- Read the message's channel before assuming iMessage-only behaviour:
  "imessage" or "sms" (RCS also reports as "sms"); null until it is sent.
  The message.* webhooks carry the same channel field (from message.sent on,
  and on message.inbound for received messages); message.queued does not.
- Optional reply_to_message_id (an inline quote) and effect (one of 13
  iMessage effects, e.g. "confetti") are iMessage only: on SMS the message
  goes out as plain text without them. reply_to_message_id must be a message
  in the same conversation (the same contact, or the same group), else 422
  reply_target_not_found. Neither is allowed with participant_phones.

ATTACHMENTS (POST /v1/messages; https://docs.ibubbl.com/guides/attachments)
- attachments: 1-10 https URLs, 1:1 only (to or contact_id; never with
  chat_guid or participant_phones). iBubbl downloads them during the request:
  set the client timeout to 60 s for these sends.
- body is optional with attachments (it is the caption), but the first
  message to a contact must have a body, else 422 first_message_needs_text.
- Types and limits: JPEG/PNG/WebP/HEIC/HEIF and GIF 25 MiB each, MP4/MOV
  50 MiB, PDF 10 MB, MP3/M4A/AAC audio 25 MiB alone; 86 MB in all. With other
  files, audio must be one MP3 of at most 2 MB, else 422
  attachment_combination_not_supported. No .ics, WebM, Ogg, SVG, ZIP.
- Errors (422, nothing created, key reusable): attachment_fetch_failed (URL
  not reachable/public, non-200, too slow), attachment_too_large,
  unsupported_attachment, attachment_combination_not_supported. The message
  names the file as attachments[i].
- The URLs, in order, are part of the idempotency payload; a replay never
  downloads again. effect and reply_to_message_id are not allowed with
  attachments (422 validation_error): send them as a separate message.
- Response: every message has attachments: [{kind, mime_type, size_bytes}]
  ([] for text). No file contents or URLs.

LIMITS (per line; https://docs.ibubbl.com/concepts/limits)
- 50 new contacts a day (resets at midnight America/New_York); the 51st gets
  422 cold_cap_exhausted.
- 30 new contacts per rolling 60 minutes; extra ones stay "pending" with
  held_reason "hourly_limit" and go out by themselves. Do not resend them.
- 1,000 outbound messages a day, every kind counted; then 422
  daily_cap_exhausted.
- 6 unanswered messages in a row to a contact who never replied; the 7th gets
  422 unresponsive_contact.
- No HTTP rate limit on POST /v1/messages (a text send never gets a 429; a
  send with attachments rarely gets 429 rate_limited, Retry-After 5), but a
  line sends one message at a time (about one a second). Send one request at
  a time per line; bursts only build a queue. Pace outreach to new contacts
  at about one every 2 minutes per line.
- A "queued" message (line offline) that expires after about an hour fires
  message.failed with status "expired" and failed_reason "queue_expired".
  Branch on failed_reason, not status. Its idempotency_key is released.

READING HISTORY (GET /v1/messages)
- Lists inbound and outbound messages, newest first, as
  {"data": [...], "has_more": bool}. limit is 1-100 (default 50).
- Next page: repeat the same request with starting_after = the id of the LAST
  item in data. Stop when has_more is false. Never use page numbers or
  offsets, and never change the filters between pages.
- Filters: contact_id, conversation_id, chat_guid, direction
  (inbound/outbound), status, created_after, created_before (ISO 8601 with
  Z, e.g. 2026-11-10T09:30:00Z; both exclusive).
- An unknown or repeated query parameter is 422 validation_error, and a
  starting_after that isn't one of your ids is 422 invalid_cursor. Use the
  exact parameter names above.
- Rate limit: 120 list requests per 60 seconds per account, shared by all
  API keys and all list endpoints. Over it: 429 rate_limited with a
  Retry-After header (seconds). Sleep that long, then send the SAME request
  again (same starting_after); the refused request didn't count. Fetch
  pages one at a time; don't run parallel loops to go faster.
- To catch up after missed webhooks: list with created_after = your last
  processed created_at minus 5 minutes, skip message ids you already have,
  and store each message's status as returned (it is current).
  https://docs.ibubbl.com/guides/webhook-retries#recover-from-downtime

CONTACTS (/v1/contacts)
- POST /v1/contacts upserts by E.164 phone (201 new, 200 existing) and only
  fills empty fields; PATCH /v1/contacts/{id} overwrites. Unknown body fields
  are 422 validation_error.
- GET /v1/contacts lists contacts newest first, with the same pagination and
  rate limit as READING HISTORY. Filters: search (every word must match a
  name, email or phone; phone digits match too), tag (ignores case),
  opted_out, blocked (true/false). Use the exact parameter names.
- DELETE /v1/contacts/{id} answers 204 with no body; afterwards the id is
  404 everywhere, including as a send's contact_id. History stays. The same
  number later becomes a NEW contact with a new id. Deleting never clears an
  opt-out.
- POST /v1/contacts/{id}/opt-out is PERMANENT: nothing can undo it. Call it
  only when the person asked not to be messaged, never to clean up a list or
  in tests with a real person's number. A repeat call is 200 with no second
  contact.opted_out event.
- To pause messages, block instead: POST /v1/contacts/{id}/block (sends get
  422 contact_blocked) and DELETE /v1/contacts/{id}/block to undo. A block
  fires no webhook and doesn't affect group conversations.
- Neither opt-out nor delete cancels a message already accepted and still
  waiting; that is done in the dashboard.

TAPBACKS, READ RECEIPTS AND TYPING
- message.inbound carries message_id and conversation_id; use them directly.
- POST /v1/messages/{message_id}/reactions {"type": "love"|"like"|"dislike"|
  "laugh"|"emphasize"|"question"}: only on a RECEIVED message (else 422
  invalid_target). 201 pending; the same type again is 200 and sends
  nothing; a different type replaces it. Delivery fires
  message.reaction.added (direction "outbound"); a failure fires
  message.reaction.failed. Not over SMS (422 sms_not_supported).
  DELETE /v1/messages/{message_id}/reactions/{reaction_id} removes it.
- POST /v1/conversations/{conversation_id}/read marks the whole conversation
  read and sends a read receipt; read_receipt is "queued" or "skipped" (with
  a reason). Never in groups or to blocked/opted-out contacts. It also
  clears the dashboard's unread badge.
- POST /v1/conversations/{conversation_id}/typing shows the typing dots for
  10 seconds: call it on message.inbound and again every 5 seconds while
  generating a reply; sending the reply clears them. status "ignored"
  (group, not_imessage, no_chat) is normal, not an error. DELETE the same
  path only if you decide not to reply.
- Tapback and typing calls to a blocked or opted-out 1:1 contact get 422
  contact_blocked / contact_opted_out; 503 opt_out_check_unavailable means
  retry later.
- These five endpoints share 300 requests per 60 seconds per account (429
  rate_limited with Retry-After), separate from the list budget.

RETRIES AND ERRORS
- Branch on detail.error (the stable code), never on detail.message.
- Log the X-Request-Id response header with every error; quote it to
  support@ibubbl.com.
- Network error, timeout or 5xx: retry with the SAME idempotency_key and
  the same payload, with exponential backoff (1 s, 2 s, 4 s) and a small cap
  on attempts. Exception: 500 no_owner_rep will not succeed on retry.
- Never retry a 4xx automatically. Fix the request instead. The one
  exception: 429 rate_limited (a list, a tapback/read/typing call, or an
  attachment send); wait Retry-After seconds, then repeat the same request
  (same idempotency_key).
- 422 unresponsive_contact: the contact has not replied to 6 messages in a
  row. Stop messaging them until they reply.
- 422 contact_opted_out: final. Never message this contact again, and never
  work around it (another number format, a group, deleting and re-creating
  the contact, another key or account).
- 422 contact_blocked: your account blocked this contact. Don't retry; only
  unblock if the user asks for it.
- 422 daily_cap_exhausted or cold_cap_exhausted: not queued. Do not send
  again before midnight ET.

WEBHOOKS
- Verify the X-iBubbl-Signature header on every request before trusting the
  body: HMAC-SHA256 of "{t}.{raw body bytes}" keyed with the whsec_ secret,
  compared in constant time; reject if t is more than 300 seconds old. Read
  the header name case-insensitively. Details:
  https://docs.ibubbl.com/guides/webhook-signatures
- Return a 2xx within 10 seconds and do the work asynchronously.
- Delivery is at least once and in no guaranteed order. Deduplicate on the
  event id (evt_...) before doing anything.
- Only message.inbound may trigger an automated send. Never send in response
  to message.queued, message.sent, message.delivered, message.read,
  message.failed, message.cancelled, message.edited or a reaction event, or
  to any event about your own outbound messages: that creates a loop.
- contact.opted_out: mark the contact do-not-contact in your own system.
- Return a 2xx for event types and enum values you don't recognise; iBubbl
  adds new ones over time.
```

The rules come from these pages, which have the details: [Message lifecycle](https://docs.ibubbl.com/concepts/message-lifecycle.md), [Limits and queues](https://docs.ibubbl.com/concepts/limits.md), [Lines and routing](https://docs.ibubbl.com/concepts/lines-and-routing.md), [Idempotency](https://docs.ibubbl.com/idempotency.md), [Errors](https://docs.ibubbl.com/errors.md), [Working hours](https://docs.ibubbl.com/concepts/working-hours.md), [Suppression and opt-outs](https://docs.ibubbl.com/concepts/suppression.md), [Webhook events](https://docs.ibubbl.com/webhooks/events.md) and [Signature verification](https://docs.ibubbl.com/guides/webhook-signatures.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.