Tintline API v1

Lines by API

Rent real cellular numbers, receive every SMS by webhook or polling. Base URL: https://your-domain/v1. All responses are JSON. Prices are in US cents and come from your account's live pricing. Everything you can do on the dashboard, you can do here.

Authentication

Create a key under Dashboard → API. Send it as a bearer token on every request. Keys start with dt_live_ and can be revoked at any time.

Authorization: Bearer dt_live_xxxxxxxxxxxxxxxxxxxxxxxx

Concepts

ObjectWhat it is
Rental (line)A number held for a month at a time. Every SMS to it is yours. Can auto-renew. Cancelling within 10 minutes (unused) refunds in full.
MessageAn inbound SMS. We extract a code (4–8 digit / G-xxxxxx / alphanumeric) and a service guess (Google, WhatsApp, …) when we can.

Statuses: active, expired, cancelled, provisioning (generating a new number), failed.

Area codes

GET /v1/area-codes

No auth required. Live inventory by area code. can_generate tells you whether rentals can provision a number in an area code we don't currently stock.

{ "area_codes": [ { "area_code": "212", "city": "New York, NY", "available": 2 }, … ], "can_generate": true }
GET /v1/pricing
{ "line_monthly_cents": 300, "activation_cents": 300, "rentals": [ { "days": 30, "price_cents": 300 } ] }

Rentals

POST /v1/rentals
FieldTypeNotes
area_codestringArea code or "any". If we have no stock there and spare lines exist, we provision a new number (status provisioning, usually a few minutes; refunded automatically on failure).
zipstringAlternative to area_code: a 5-digit US ZIP. We match the nearest stocked area code in that metro/state, or provision a new line for that ZIP.
days30Lines are monthly. First month is charged plus a one-time activation fee (see /v1/pricing); renewals are the monthly price only.
labelstringOptional, for your reference.
auto_renewbooleanRenew for the same term at expiry while balance allows.
GET /v1/rentals
GET /v1/rentals/{id}

Includes the latest 200 messages.

POST /v1/rentals/{id}/renew

Body { "days": 30 }. Also reactivates an expired rental within 48 hours if the number hasn't been reassigned.

POST /v1/rentals/{id}/cancel

Messages

GET /v1/messages?number=2125550147&since=2026-10-13T00:00:00Z&limit=50
{ "messages": [ { "id": "…", "number": "2125550147", "from": "22000", "text": "G-482913 is your Google verification code.",
    "code": "482913", "service": "Google", "received_at": "2026-10-13T04:02:11.000Z" } ], "now": "…" }

Webhooks

Set an HTTPS URL under Dashboard → API. We POST every attributed message within a second of arrival and retry 3 times. If you set a secret, the header X-Tintline-Signature is the hex HMAC-SHA256 of the raw body.

{ "event": "message.received",
  "message": { "id": "…", "number": "2125550147", "from": "22000", "text": "…", "code": "482913", "service": "Google", "received_at": "…" },
  "verification_id": "b1c6…", "rental_id": null }
// Node: verify signature
const crypto = require('crypto');
const expected = crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-tintline-signature'] || ''));

Errors & limits

StatusMeaning
400Validation error — error explains.
401 / 403Missing or revoked key; unverified or suspended account.
402Insufficient balance. Body includes needed (cents). Top up on the dashboard.
409No inventory for that area code. Body includes coverage with alternatives.
429Rate limited: 600 req/min per key, 10 concurrent lines (contact sales for more).
502Carrier gateway error (number generation). Nothing was charged.

Examples

# Rent a line near a ZIP code, auto-renewing monthly
curl -s -X POST $BASE/v1/rentals -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"zip":"11213","auto_renew":true,"label":"ops-01"}'

# Read its messages
curl -s "$BASE/v1/messages?number=3475550184&limit=20" -H "Authorization: Bearer $KEY"
// JavaScript
const base = 'https://your-domain/v1', h = { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' };
const line = await (await fetch(`${base}/rentals`, { method: 'POST', headers: h, body: JSON.stringify({ zip: '11213', auto_renew: true }) })).json();
console.log('number', line.number_formatted);
// then listen on your webhook, or poll:
const msgs = await (await fetch(`${base}/messages?number=${line.number}`, { headers: h })).json();