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
| Object | What 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. |
| Message | An 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
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 }
{ "line_monthly_cents": 300, "activation_cents": 300, "rentals": [ { "days": 30, "price_cents": 300 } ] }
Rentals
| Field | Type | Notes |
|---|---|---|
area_code | string | Area 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). |
zip | string | Alternative 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. |
days | 30 | Lines are monthly. First month is charged plus a one-time activation fee (see /v1/pricing); renewals are the monthly price only. |
label | string | Optional, for your reference. |
auto_renew | boolean | Renew for the same term at expiry while balance allows. |
Includes the latest 200 messages.
Body { "days": 30 }. Also reactivates an expired rental within 48 hours if the number hasn't been reassigned.
Messages
{ "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
| Status | Meaning |
|---|---|
| 400 | Validation error — error explains. |
| 401 / 403 | Missing or revoked key; unverified or suspended account. |
| 402 | Insufficient balance. Body includes needed (cents). Top up on the dashboard. |
| 409 | No inventory for that area code. Body includes coverage with alternatives. |
| 429 | Rate limited: 600 req/min per key, 10 concurrent lines (contact sales for more). |
| 502 | Carrier 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();