Texttive API
A small, predictable REST API for sending texts, checking numbers and keeping consent records. Base URL: https://api.texttive.com/api/v1
Authentication
Create an API key on the Developers page and send it as a bearer token. Keys are scoped read or read + write and act on one account. Limit: 120 requests per minute per key (429 rate_limited beyond that).
curl https://api.texttive.com/api/v1/me \
-H 'Authorization: Bearer ttv_live_xxxxxxxx'
Responses & errors
Successful responses wrap the resource in data; lists add links and meta for pagination. Errors are always:
{ "error": { "code": "validation_failed", "message": "The request is invalid.", "details": { "to": ["Enter a valid phone number."] } } }
| HTTP | code | Meaning |
|---|---|---|
| 401 | unauthenticated | Missing or revoked key |
| 403 | insufficient_scope | Key lacks the write scope |
| 403 | account_suspended | Account suspended |
| 402 | insufficient_funds | Wallet cannot cover a lookup |
| 404 | not_found | No such resource in this account |
| 409 | idempotency_conflict | Idempotency-Key reused with a different payload |
| 422 | validation_failed | Bad input; see details |
| 422 | invalid_url | Webhook URL rejected (https + public host only) |
| 429 | rate_limited | Slow down |
Messages
POST /messages queues a message through the same pipeline as the portal: compliance gate, pricing, quiet hours. The response is 201 with the message; check status: queued, scheduled (deferred past quiet hours or by scheduled_at) or blocked (see error). Send an Idempotency-Key header to make retries safe.
curl -X POST https://api.texttive.com/api/v1/messages \
-H 'Authorization: Bearer ttv_live_xxxxxxxx' \
-H 'Idempotency-Key: order-1042-confirm' \
-H 'Content-Type: application/json' \
-d '{
"from": "+14055550100",
"to": "+14055550199",
"body": "Acme: your order #1042 is ready for pickup. Reply STOP to opt out.",
"kind": "transactional",
"client_ref": "order-1042"
}'
kind — marketing (default; held 9pm–8am in the account timezone) or transactional (sends any time). media — array of public image URLs, sends as MMS. scheduled_at — ISO 8601, interpreted in the account timezone if no offset.
GET /messages lists newest first with filters status, direction (in/out), to, from, since, client_ref, per_page (≤100). GET /messages/{id} returns one.
{ "data": { "id": 812, "direction": "out", "kind": "transactional", "status": "delivered", "from": "+14055550100", "to": "+14055550199", "body": "…", "segments": 1, "price": "0.015000", "error": null, "client_ref": "order-1042", "created_at": "2026-09-17T18:04:51+00:00", "sent_at": "…", "delivered_at": "…" } }
Numbers
GET /numbers lists your active numbers with can_send (true once 10DLC / toll-free registration is approved) and registration text. Buy and release numbers in the portal.
Carrier lookup
POST /lookups {"phone": "..."} returns carrier, line type and deliverable. Uses your free credits first, then $0.01 per lookup from the wallet; repeats within 30 days are free (source: "cache"). Malformed input returns line_type: "invalid" at no charge.
{ "data": { "phone": "+14055550122", "valid": true, "carrier": "Verizon Wireless", "line_type": "mobile", "deliverable": true, "price": "0.000000", "source": "provider" } }
Opt-outs
Anyone who texts STOP is blocked automatically and every send is checked against this list. GET /opt-outs lists active opt-outs, GET /opt-outs/{phone} checks one number, POST /opt-outs records one (e.g. from your own unsubscribe page), DELETE /opt-outs/{phone} removes it — only with the person's renewed consent.
Contacts
POST /contacts upserts by phone (201 created / 200 updated) with first_name, last_name, email, free-form attributes, and opted_in_source to record consent. Also GET /contacts?q=, GET/PATCH/DELETE /contacts/{id}.
Webhooks
Register an https endpoint (portal or POST /webhooks) and choose events. We POST JSON and sign it; verify before trusting it.
message.sent | Message handed to the carrier |
message.delivered | Message delivered to the handset |
message.undelivered | Carrier could not deliver |
message.failed | Message failed before leaving Texttive |
message.blocked | Message blocked by the compliance gate |
message.received | Inbound message received |
optout.recorded | Recipient opted out (STOP / manual / API) |
optout.reversed | Recipient opted back in |
POST /your/endpoint
Content-Type: application/json
X-Texttive-Event: message.delivered
X-Texttive-Delivery: 7f5d…
X-Texttive-Signature: t=1758132291,v1=5c1a…
{ "id": "7f5d…", "event": "message.delivered", "created_at": "…", "data": { "message": { … } } }
Signature = HMAC-SHA256(secret, "<t>.<raw body>"). Reject if t is older than 5 minutes. Respond with any 2xx within 10 seconds; otherwise we retry up to 6 times (1 min → 3 h). After 15 consecutive failures the endpoint is disabled until you re-enable it.
// PHP
[$t, $v1] = [substr($parts[0], 2), substr($parts[1], 3)]; // from 't=…,v1=…'
$ok = hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), $v1) && abs(time() - $t) < 300;
OpenAPI
Machine-readable spec: https://api.texttive.com/docs/openapi.json