Texttive

Texttive API

A small, predictable REST API for sending texts, checking numbers and keeping consent records. Base URL: https://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://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."] } } }
HTTPcodeMeaning
401unauthenticatedMissing or revoked key
403insufficient_scopeKey lacks the write scope
403account_suspendedAccount suspended
402insufficient_fundsWallet cannot cover a lookup
404not_foundNo such resource in this account
409idempotency_conflictIdempotency-Key reused with a different payload
422validation_failedBad input; see details
422invalid_urlWebhook URL rejected (https + public host only)
429rate_limitedSlow 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://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"
  }'

kindmarketing (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.sentMessage handed to the carrier
message.deliveredMessage delivered to the handset
message.undeliveredCarrier could not deliver
message.failedMessage failed before leaving Texttive
message.blockedMessage blocked by the compliance gate
message.receivedInbound message received
optout.recordedRecipient opted out (STOP / manual / API)
optout.reversedRecipient 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://texttive.com/docs/openapi.json