iBlueText API

Base URL https://ibluetext.com/api/v1 · JSON over HTTPS.

Introduction

The iBlueText API lets you validate which numbers are on iMessage and send real blue-bubble messages from your dedicated line — straight from your own app, backend, or CRM. Every request is authenticated with an API key and returns JSON.

Which API for what

All endpoints share the same base URL and Bearer-key auth — they're grouped here by purpose so you pick the right one. Full details for each are in the sections below.

PurposeEndpoint
iPhone Lookup — is a number on iMessage?POST/GET /api/v1/lookup
Send — send an iMessage/SMSPOST/GET /api/v1/send
SMS Verify / 2FA — send & check a codePOST /api/v1/verify · /siteverify · /sites
Conversations — read full in/out message history (for CRMs / agents)GET /api/v1/conversations
Verified contacts — export widget-verified numbersGET /api/v1/contacts
Balance & usage — credits + per-line daily capsGET /api/v1/balance

Get a key under Integrations → API keys. Every request uses Authorization: Bearer <key> and only ever sees your own account's data.

For AI agents 🤖

iBlueText is built to be set up by an AI agent. A human verifies once (the only human step), then your agent does the rest via the API. There's a machine-readable summary at ibluetext.com/llms.txt.

Paste this to your agent (Claude or any) to get started:

I'm setting up iBlueText (https://ibluetext.com) for SMS/iMessage.
Read https://ibluetext.com/llms.txt and https://ibluetext.com/docs/.
I'll verify I'm human via SMS and give you my API key
(send it as: Authorization: Bearer <key>). Then:
  1) create a verify-widget site (POST /api/v1/sites) and give me the embed snippet,
  2) help me run an iPhone lookup (POST /api/v1/lookup) on my list,
  3) set up sending (POST /api/v1/send) as I describe.

Your agent can create widget sites, run lookups, send messages, and pull contacts entirely through the API — see the endpoints below.

Authentication

Create a key under Settings → API keys, then send it as a Bearer token on every request. Keep keys secret — anyone with a key can spend your credits.

Authorization: Bearer ibt_live_xxxxxxxxxxxxxxxxxxxxxxxx

Quickstart

Check whether three numbers are on iMessage:

curl https://ibluetext.com/api/v1/lookup \
  -H "Authorization: Bearer $IBT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numbers": ["+18135550142", "+17275550199", "+13055550123"]}'
{ "id": 8412, "status": "queued", "total": 3,
  "poll_url": "https://ibluetext.com/api/v1/lookup?id=8412" }

Then poll the poll_url until status is done.

POST/lookup

Submit numbers to validate. Numbers are de-duplicated and must include a country code. One lookup credit is spent per number (your first 100 are free).

Body

{ "numbers": ["+18135550142", "+17275550199"] }   // array, or comma-separated string

Response 201

{ "id": 8412, "status": "queued", "total": 2, "poll_url": "…/lookup?id=8412" }

GET/lookup?id=<id>

Fetch job progress and results. imessage is true (blue bubble), false (SMS), or null (not yet checked).

{ "id": 8412, "status": "done", "total": 2, "completed": 2, "iphones": 1,
  "results": [
    { "number": "+18135550142", "imessage": true,  "checked": true },
    { "number": "+17275550199", "imessage": false, "checked": true }
  ] }

POST/send

Queue an iMessage from your active sending line. Requires a provisioned line. Recipients who replied STOP are blocked automatically.

Body

{ "to": "+18135550142",
  "body": "Hi {firstname}! Your order shipped 🎉",
  "first_name": "Sarah",       // optional — fills {firstname}
  "line_id": 12,                // optional — defaults to your first active line
  "effect": "confetti",         // optional — iMessage effect (see below)
  "typing": true }              // optional — show the "…" typing bubble first

Response 201

{ "id": 99213, "to": "+18135550142", "status": "queued", "poll_url": "…/send?id=99213" }

GET/send?id=<id>

{ "id": 99213, "to": "+18135550142", "channel": "imessage", "status": "delivered", "sent_at": "2026-06-17 14:02:11" }

Status values: queued → sent → delivered, or failed.

iMessage effects, typing & reactions

The things that make a text feel like it came from a person on an iPhone. They only exist in iMessage: if a message ends up going by SMS (Android), the text still sends and the extra is simply skipped.

Effects — "effect" on /send

Full-screen: confetti, balloons, fireworks, love (hearts), celebration, lasers, echo, spotlight. Bubble: slam, loud, gentle, invisible_ink. The effect plays when the recipient opens the message on an iPhone. Also in the dashboard: the ✨ picker in the Inbox and on Campaigns.

Typing bubble — "typing" on /send

true shows the "…" bubble for a couple of seconds (scaled to the message length) before it arrives; or pass seconds, 1–8. Only in an existing iMessage conversation — a first message to someone has no thread to type into, so it just sends. Built-in AI replies already do this automatically.

POST/react — tapbacks

React to a message someone sent you. Use the message_id from the webhook or the conversations API, or pass to to react to their latest message.

{ "message_id": "in_3909", "reaction": "love" }
{ "to": "+18135550142", "reaction": "like", "line_id": 12 }   // line_id optional

Reactions: love ❤️, like 👍, dislike 👎, laugh 😂, emphasize ‼️, question ❓. Prefix with - (e.g. "-love") to take it back. Response 201: { "id": 51, "status": "queued", "reaction": "love", "message_id": "in_3909", "poll_url": "…/react?id=51" }; GET /react?id=51 → queued / sent / failed with an error. iMessage conversations only (an SMS thread fails with a clear error). Reactions are free and don't count toward line limits. In the dashboard, hover any incoming message in the Inbox (tap on a phone) to react.

SMS Verify — widget & API

Add "verify you're human via SMS" to any form. Two ways:

Drop-in widget (easiest)

Create a site under SMS Verify to get a site_key, then paste this above your form's submit button:

<div class="ibt-verify" data-sitekey="ibt_site_xxx" data-theme="auto"></div>
<script src="https://ibluetext.com/v.js" async></script>

The widget gates your submit button until verified, then injects a hidden ibt_token field. Themes: auto / light / dark. It also emits an ibt-verified DOM event.

Where to place it: any form that collects a phone (signup, contact, checkout); the "add your phone" step after a Google/Apple login (social login gives you an email but no verified phone); or as a gatekeeper — render it on a page and only grant access (or call your backend) once the ibt-verified event fires or /siteverify confirms the token.

Capturing name & email: the widget auto-reads the name + email from the surrounding form and attaches them to the verified contact (so a contact form becomes name+email+verified-phone). Add data-require="name,email" to require those fields before the user can verify, or data-name-field/data-email-field (CSS selectors) to target specific inputs. Omit for phone-only capture.

Exactly where in the page:

For one-tap autofill (recommended): add autocomplete tokens to your own fields — name field autocomplete="name", email field type="email" autocomplete="email". The widget's phone input already has autocomplete="tel". With all three, iOS "AutoFill Contact" fills name + email + phone in one tap, and the verification code one-tap-fills on iPhone / auto-fills zero-tap on Android Chrome. The code field needs nothing extra — the widget handles it.

REST API (server-side)

# 1. start — send a code (server uses your secret API key)
curl https://ibluetext.com/api/v1/verify \
  -H "Authorization: Bearer $IBT_KEY" -H "Content-Type: application/json" \
  -d '{"phone":"+18135550142","page_url":"https://you.com/signup"}'
# -> { "id": 5512, "status": "queued" }

# 2. check — confirm the code the user typed
curl "https://ibluetext.com/api/v1/verify?action=check" \
  -H "Authorization: Bearer $IBT_KEY" -H "Content-Type: application/json" \
  -d '{"id":5512,"code":"481920"}'
# -> { "verified": true, "token": "…", "phone": "+18135550142", "channel": "imessage" }

The code is delivered by iMessage (iPhone) or SMS (Android) automatically. Each verification logs a proof-of-consent record (number, time, IP, page, consent text).

POST/siteverify

Confirm a widget token on your backend with your secret key (like Cloudflare's siteverify). Tokens are single-use.

curl https://ibluetext.com/api/v1/siteverify \
  -H "Content-Type: application/json" \
  -d '{"secret":"ibt_secret_xxx","token":""}'
# -> { "success": true, "phone": "+18135550142", "channel": "imessage", "verified_at": "…" }

GET/contacts

Pull your verified contacts — phone, name, email, channel, and verified time — to sync into your own database or CRM. Paginate with next_since.

curl "https://ibluetext.com/api/v1/contacts?since=0&limit=200" \
  -H "Authorization: Bearer $IBT_KEY"
# -> { "contacts":[ { "id":91, "phone":"+18135550142", "name":"Sarah Lee",
#        "email":"sarah@x.com", "channel":"imessage", "verified_at":"…" } ],
#      "count":1, "next_since":91 }

You can also download everything as CSV from SMS Verify → Download contacts.

Billing & the 402 "out of credits" error

The first 100 credits are free for testing. 1 credit = $0.005, spent by either one iPhone lookup or one SMS verification (shared pool). Separately, your first 10 SMS verifications are free (no card) — a distinct trial bucket from the 100 lookup credits. Sending iMessage/SMS instead needs an active line subscription ($99/mo + $249 setup), not credits.

When you run out, the paid endpoints return HTTP 402 with a machine-readable body so an AI agent knows exactly what to do:

{ "error": "payment_required",
  "reason": "insufficient_credits",      // or "subscription_required"
  "message": "…buy credits to continue.",
  "action": "lookup",                     // or "verify" / "send"
  "needed": 250, "available": 0, "unit_price_usd": 0.005,
  "human_action_required": true,          // a HUMAN must pay — agents can't enter a card
  "buy_url": "https://ibluetext.com/portal/billing.php" }

Agents: on a 402, stop and tell your human to pay at buy_url, then resume. Buy credits · Subscribe a sending line.

Check your wallet + per-line daily usage anytime: GET /api/v1/balance → credits, free-trial + free-daily lookups remaining, and each line's new-contact / non-replier counts. Poll it to warn before $0 or a daily cap.

Sending limits

To keep your number safe from carrier/Apple bans, each line is capped per customer-local day (resets 4am your time):

Webhooks — connect your own reply bot

Every text that comes in on your line is POSTed to your server within seconds — campaign replies, new conversations, photos and voice memos, opt-outs. Use it to let your own system answer with your customers' data: appointment confirmations ("Reply Y to confirm"), showings, order status, account balances — anything our built-in AI can't know. Your bot (written by your developer, or by your own AI assistant) replies through POST /api/v1/send, and the whole conversation still shows in your Inbox so you can jump in anytime.

Where to set it: Settings → Webhook for the whole account, or Lines → Reply webhook for one line (overrides the account webhook — handy when each line serves a different business or campaign). Tick "Replies handled by my webhook" to switch our built-in AI off, so nobody ever gets two answers. Send test posts a message.test event so you can check your endpoint.

What your bot needs — 3 things: (1) the webhook URL — this comes from your bot (where it receives texts); (2) your signing secret — Settings → Webhook, with a Copy button; (3) an API key so it can reply — Settings → API keys (shown once).

Let your AI set it up — one API key, no logins

Easiest of all: make an API key, give it to your AI assistant or developer, and say "connect my reply bot to iBlueText — see ibluetext.com/docs/#webhooks-api". With that one key it can read your signing secret and line IDs, save its own webhook URL, and send itself a test. It never needs your password, and the key can only touch webhook settings here (no billing, no account details). Revoke the key in Settings any time.

GET/api/v1/webhooks — signing secret, account webhook, and every line (id, number, reservation, webhook)

curl https://ibluetext.com/api/v1/webhooks -H "Authorization: Bearer $IBT_KEY"

{ "signing_secret": "09a936…",
  "account_webhook": { "url": null, "replies_handled_by_webhook": false },
  "lines": [ { "line_id": 2, "label": "bluesend03", "phone": "+17276439408",
               "reserved_until": "2026-10-15T13:00:00Z", "reserved_note": "BCI campaign",
               "webhook_url": null, "replies_handled_by_webhook": false } ],
  "events": ["message.reply", "message.optout", "message.test"], ... }

POST/api/v1/webhooks — save the URL (and optionally test it)

curl -X POST https://ibluetext.com/api/v1/webhooks -H "Authorization: Bearer $IBT_KEY" \
  -H "Content-Type: application/json" -d '{
  "url": "https://yourbot.com/sms-webhook?key=your-own-secret",
  "lines": "reserved",                  // "account" (default) | "all" | "reserved" | [2, 4, 9]
  "replies_handled_by_webhook": true,   // default true — built-in AI stays quiet
  "test": true                          // POST a message.test to the URL after saving
}'
→ { ...same as GET..., "updated": 7, "test": { "ok": true, "http_code": 200 } }
POST https://yourapp.com/sms-webhook?key=your-own-secret
Content-Type: application/json
X-IBT-Signature: 5d41402abc4b2a76b9719d911017c592...   // hex HMAC-SHA256 of the raw body

{ "event": "message.reply",              // or "message.optout" (STOP etc.), "message.test"
  "message_id": "in_48213",              // unique per inbound message, identical on retries
  "from": "+18135550142",                // E.164
  "to": "+17276439408",                  // your line that received it
  "line_id": 2,
  "channel": "imessage",                 // "imessage" | "sms"
  "body": "Yes please!",
  "attachments": [ { "url": "https://ibluetext.com/m.php?...", "mime": "image/jpeg", "name": "IMG_0142.jpg" } ],
  "received_at": "2026-10-12T14:03:22Z", // ISO-8601 UTC
  "campaign_id": 123,                    // last campaign that texted this number, or null
  "fields": { "first_name": "Jane", "shares": "40" } }  // that contact's CSV columns, or {}

Don't have a developer? Give your own AI assistant (Claude, ChatGPT) an API key and this page with "build me a reply bot for my website that answers from my customer database, and connect it using /api/v1/webhooks", or have us build it for you (from $299 setup).

Opt-outs

Compliance is automatic. Any reply containing STOP, UNSUBSCRIBE, CANCEL, QUIT, or END adds that number to your opt-out list, and the API will refuse to send to it (409). This is required under the TCPA — see our Terms.

Conversations API

Pull full two-way transcripts so your own app or AI agent can read what each contact said and act on it (e.g. update a CRM, or add/remove a listing on another site). iBlueText returns the raw in/out messages — your side does the judging.

GET/api/v1/conversations

Query params: phone (one conversation), since (ISO time — only conversations with newer activity, for incremental polling), limit (default 200, max 1000), include_no_reply=1 (also include contacts who never replied; default is repliers only).

curl "https://ibluetext.com/api/v1/conversations?limit=200" \
  -H "Authorization: Bearer $IBT_KEY"

{
  "count": 1,
  "conversations": [
    {
      "phone": "+18139188356",
      "company_name": "FinSanity Fishing Charters",
      "link": "https://beercanisland.com/c/0XCtDU1a",
      "fields": { "company_name": "FinSanity Fishing Charters", "link": "https://beercanisland.com/c/0XCtDU1a" },
      "opted_out": false,
      "last_activity": "2026-07-11 19:39:04",
      "messages": [
        { "dir": "out", "body": "Hi is this FinSanity Fishing Charters?", "at": "2026-07-11 19:35:00", "channel": "sms", "status": "sent" },
        { "dir": "in",  "body": "Yes, it is", "at": "2026-07-11 19:37:17" },
        { "dir": "out", "body": "Here's your link: https://beercanisland.com/c/0XCtDU1a", "at": "2026-07-11 19:39:40", "channel": "sms", "status": "sent" }
      ]
    }
  ]
}

Each conversation carries the contact's uploaded fields (including company_name and link), an opted_out flag, and the ordered messages (dir is in or out). Generate a key under Integrations → API keys.

AI auto-replies

Turn on Inbound AI and your line answers incoming texts automatically — trained on your business, and optionally on per-contact data you provide. Two modes:

Give the AI a per-contact database

When you send a campaign, include a CSV with a phone column plus any fields you want. Each row is stored against that number and handed to the AI when the person replies:

company,phone,loan_amount,rate,due_date,link
Reel Deal LLC,+18135550142,$45000,6.9%,Aug 1,https://you.com/portal/abc123
Salty Dog LLC,+17275550199,$12000,8.4%,Sep 15,https://you.com/portal/def456

Now if +18135550142 texts "what's my rate and my link?", the AI replies with 6.9% and that contact's link. Guide it from your Instructions, e.g. "If a client asks about their loan, share the amount, rate, and due date from their info; if you don't have a detail, say a teammate will follow up."

Safety: replies never cross contacts

Every reply is scoped to the exact E.164 phone number that texted in — the model only ever receives that one contact's fields, so John can't get Jane's data. The AI is also instructed never to invent account details (amounts, rates, balances, links); if it doesn't have a value on file, it defers to a human instead of guessing. STOP/opt-outs are never answered.

CRM & integrations

Because everything runs over a standard REST API, iBlueText drops into any stack:

Native one-click connectors are rolling out — tell us which you need at russ@ibluetext.com.

SMPP

Already running a messaging platform? Connect to iBlueText over SMPP v3.4 — the same protocol you use with SMS carriers — and send iMessage (and green RCS/SMS) without writing to our REST API. Credentials are self-serve: create a bind under SMPP in your dashboard and it goes live within about a minute.

Hostsmpp.ibluetext.com
Port3550 — TLS only (ssmpp). Plaintext SMPP is not offered.
VersionSMPP v3.4, bind_transceiver recommended
EncodingsGSM 03.38 (0x01), Latin-1 (0x03), UCS2 (0x08)

Trunks

Each bind is a trunk, and you pick the route by choosing which bind you submit on:

Submitting

Delivery receipts

Set registered_delivery=1. Receipts come back as deliver_sm with esm_class 0x04 and the conventional text format:

id:12345 sub:001 dlvrd:001 submit date:2609221200 done date:2609221205 stat:DELIVRD err:000 text:

What you can expect depends on how the message actually travelled — which we report honestly rather than guessing:

iMessagereal confirmation → DELIVRD
RCSreal confirmation → DELIVRD
SMSno delivery receipt exists → ACCEPTD only

We deliver green — we don't select RCS. The handset and the two carriers negotiate RCS vs plain SMS per recipient. We can tell you which happened, but not choose it.

Throughput

Each bind has an agreed TPS limit; exceeding it returns ESME_RTHROTTLED (0x58) — that's flow control, not an error, so back off and retry. Size your expectations to the delivery rate, not your submission rate: you can submit far faster than messages go out, and the queue absorbs the difference. The same per-line daily caps apply to SMPP traffic as to everything else, so if you need a specific throughput, tell us up front and we'll provision enough lines.

Errors & limits

Errors return a JSON error string with a standard HTTP status:

401 invalid/missing key   ·   402 out of credits / no line
400 bad request           ·   404 not found
409 recipient opted out   ·   429 rate limited

Up to 50,000 numbers per lookup request. Sends are paced by your line's safe throughput. Need higher volume? Add more lines.

Get your API key — start free →