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.
| Purpose | Endpoint |
|---|---|
| iPhone Lookup — is a number on iMessage? | POST/GET /api/v1/lookup |
| Send — send an iMessage/SMS | POST/GET /api/v1/send |
| SMS Verify / 2FA — send & check a code | POST /api/v1/verify · /siteverify · /sites |
| Conversations — read full in/out message history (for CRMs / agents) | GET /api/v1/conversations |
| Verified contacts — export widget-verified numbers | GET /api/v1/contacts |
| Balance & usage — credits + per-line daily caps | GET /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:
- Inside a form (contact, signup, lead) → put the widget div inside the
<form>…</form>tags, after the name/email inputs and just before the submit button. It auto-finds its parent<form>, gates that form's submit button, and reads the name/email from it. Example (BCI contact form): place it after Email, before Subject. - Standalone / gatekeeper (outside any form) → if it's not inside a
<form>, it verifies phone only (nothing to gate or capture). Listen for theibt-verifiedevent to unlock the page/content.
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):
- 50 new iMessage + 50 new SMS first-contacts/day/line (just 15/day each during a line's first 5 days — warm-up).
- 150/channel/day total to numbers that haven't replied yet.
- Unlimited 2-way — once someone replies (and hasn't sent STOP), message them as much as you like.
- Over-cap sends hold and auto-resume the next day (drip). More lines = higher daily ceiling.
- STOP / unsubscribe auto-suppresses that number for your account, permanently.
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 } }
"url": ""clears it."rotate_secret": truemakes a new signing secret (update your bot right after)."account"= every line on the account; a line's own webhook overrides it. Use"reserved"for a short campaign on lines reserved for you, or a list ofline_ids.message.testcomes from a fake number (+15550100099) — don't reply to it.
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 {}
- Respond fast: answer
2xxwithin 8 seconds, then do the slow work (AI, database lookups) in the background. The response body is ignored. - Retries: a timeout or non-2xx is retried 3 more times (after ~30s, 2 min, 10 min) with the same
message_id— de-duplicate on it. Recent deliveries and errors show under Settings → Webhook. - Auth: put a secret in your URL (
?key=…), and/or verifyX-IBT-Signature= hex HMAC-SHA256 of the raw request body with your account's signing secret (shown in Settings → Webhook).X-iBlueText-Signaturecarries the same value for older integrations. - Opt-outs: STOP / UNSUBSCRIBE / CANCEL / QUIT / END are still handled for you (number suppressed, no reply sent) and arrive as
message.optoutso your records match. - Replying:
POST /api/v1/send {"to","body"}— the body is sent exactly as given, replies go out on the same line as the conversation, and replies to someone who texted you are never capped. Texting a brand-new number counts toward that line's daily new-contact cap; passline_idto choose the line. - Media: attachment URLs are signed, unguessable links you can fetch without your API key; to send media back, pass
mediato/api/v1/send.
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:
- General — answers from your business info (hours, pricing, FAQs, booking). Set it up on the AI Replies page: a business name, instructions, and a knowledge base (paste it, upload a
.txt, or auto-build from your website). - Account-specific — answers questions about the individual texting in, using data attached to their phone number. Upload a CSV with your campaign and every column becomes part of that contact's record.
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:
- GoHighLevel — trigger sends from workflows and sync replies back as conversations.
- HubSpot / Salesforce — fire a lookup or send on a contact event.
- Zapier / Make / n8n — connect to 5,000+ apps with no code via webhooks.
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.
| Host | smpp.ibluetext.com |
| Port | 3550 — TLS only (ssmpp). Plaintext SMPP is not offered. |
| Version | SMPP v3.4, bind_transceiver recommended |
| Encodings | GSM 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:
- Auto — iMessage when the recipient supports it, green otherwise, with automatic fallback if an iMessage fails to deliver. This is the one most customers want.
- Green only — always leaves as RCS or SMS, never attempts iMessage.
- iMessage only — blue or nothing.
Submitting
destination_addr— E.164 preferred (+13035551234); setdest_addr_ton=1for international. Bare 10-digit US numbers are accepted.- Long messages — segment with UDH as normal. We reassemble the parts and deliver one message, because iMessage has no 160-character limit.
source_addr— accepted but not used. Messages send from your line's own number; alphanumeric sender IDs are not supported.submit_sm_respmeans accepted into the queue, not delivered. Delivery is reported by DLR.
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:
| iMessage | real confirmation → DELIVRD |
| RCS | real confirmation → DELIVRD |
| SMS | no 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.