API reference
JSON over HTTPS. Authenticate with Authorization: Bearer s72_live_… from your dashboard's API keys page.
Base URL https://star72.com/v1
Messaging
POST/v1/messages (POST /v1/messages)
scope: messages:write
Send an SMS or MMS. Body: from, to, text and/or media_urls. Optional Idempotency-Key header.
{ "from": "+18455550100", "to": "+12125550199", "text": "Your table is ready." }
GET/v1/messages (GET /v1/messages)
scope: messages:read
List recent messages, newest first. ?limit=1..100.
GET/v1/messages/{id} (GET /v1/messages/{id})
scope: messages:read
One message with its current status.
POST/v1/consents (POST /v1/consents)
scope: messages:write
Record opt-in evidence. Body: phone, method, evidence, captured_at.
Numbers
GET/v1/phone-numbers/available (GET /v1/phone-numbers/available)
scope: numbers:read
Search numbers to buy. ?area_code=845 or ?state=NY.
POST/v1/phone-numbers (POST /v1/phone-numbers)
scope: numbers:write
Buy a number. Body: phone_number, and optionally voice { mode, forward_to, greeting, sip_uri } (as on PUT /voice; the SIP secret is returned when given). Charges setup and the first month.
GET/v1/phone-numbers (GET /v1/phone-numbers)
scope: numbers:read
Your numbers and their campaign link status.
POST/v1/phone-numbers/{id}/campaign (POST /v1/phone-numbers/{id}/campaign)
scope: numbers:write
Attach a number to an approved campaign. Body: campaign_id.
PUT/v1/phone-numbers/{id}/voice (PUT /v1/phone-numbers/{id}/voice)
scope: numbers:write
How the number answers calls. Body: mode (off, forward, voicemail, sip), forward_to, greeting, sip_uri, rotate_secret. In sip mode calls go to your phone system with X-Star72-Number, X-Star72-Call and X-Star72-Secret headers (also sent as X-Kestrel-* for older setups); the secret is returned here. While the carrier is still updating call routing, voice.routing_pending is true (retried automatically).
DELETE/v1/phone-numbers/{id} (DELETE /v1/phone-numbers/{id})
scope: numbers:write
Release a number back to the carrier. Billing stops and calls and texts to it end. Cannot be undone.
Porting
POST/v1/ports/check (POST /v1/ports/check)
scope: numbers:read
Whether numbers can move to us from their current carrier. Body: phone_numbers. Returns portable, fast_portable and the reason when not.
{ "phone_numbers": ["+12125550100"] }
POST/v1/ports (POST /v1/ports)
scope: numbers:write
Start a port. Body: phone_numbers, plus any of the details below. Checks portability first. Returns { data: [port] }; the carrier may split numbers from different carriers into separate ports.
PATCH/v1/ports/{id} (PATCH /v1/ports/{id})
scope: numbers:write
Add or correct details: entity_name (exactly as on the bill), authorized_name, account_number, pin, billing_phone_number, current_carrier, service_address { street_address, extended_address, city, state, postal_code }, requested_foc_date, port_type (full, partial) with remaining_numbers_action (keep, disconnect) and new_billing_phone_number, voice { mode, forward_to, sip_uri, greeting } applied when the numbers arrive, customer_reference. The account number and PIN go to the carrier and are not stored.
POST/v1/ports/{id}/documents (POST /v1/ports/{id}/documents)
scope: numbers:write
Attach the letter of authorization or a recent bill: send the file itself (Content-Type application/pdf, image/png or image/jpeg, up to 4 MB) with ?type=loa or ?type=invoice. Or send JSON { "type": "loa", "signature": { "name", "agreed": true, "signed_at", "ip", "user_agent" }, "text" } and we render the signed letter (our standard wording when text is left out).
POST/v1/ports/{id}/submit (POST /v1/ports/{id}/submit)
scope: numbers:write
Submit to the carrier once nothing is missing (see missing on the port). Resubmit the same way after fixing an exception. Charges the port fee, if any, once.
GET/v1/ports (GET /v1/ports)
scope: numbers:read
Your ports, newest first.
GET/v1/ports/{id} (GET /v1/ports/{id})
scope: numbers:read
One port: status (draft, in_process, submitted, exception, foc_confirmed, ported, cancel_pending, cancelled, deleted), foc_date, exceptions with what to fix, missing, and numbers with their phone-number ids once ported.
POST/v1/ports/{id}/cancel (POST /v1/ports/{id}/cancel)
scope: numbers:write
Cancel a port. Not possible online within 48 hours of the move date.
GET/v1/ports/{id}/comments (GET /v1/ports/{id}/comments)
scope: numbers:read
Messages with the carrier porting team. POST the same path with { "body" } to reply.
10DLC registration
POST/v1/brands (POST /v1/brands)
scope: brands:write
Register your business with The Campaign Registry. Body: entity_type, vertical, display_name, legal_name, ein, phone, email, website, street, city, state, postal_code. Charges the brand fee.
GET/v1/brands (GET /v1/brands)
scope: brands:write
Your brands and their status.
GET/v1/brands/{id} (GET /v1/brands/{id})
scope: brands:write
Brand status: pending, unverified, verified, failed.
POST/v1/campaigns (POST /v1/campaigns)
scope: campaigns:write
Submit a campaign: use_case, description, message_flow, samples, help_message, opt_out_message.
GET/v1/campaigns (GET /v1/campaigns)
scope: campaigns:write
Your campaigns and their status.
GET/v1/campaigns/{id} (GET /v1/campaigns/{id})
scope: campaigns:write
Campaign status: pending, unconfirmed (we are checking with the carrier), accepted, active, failed, expired.
Voice
POST/v1/calls (POST /v1/calls)
scope: calls:write
Place an automated call from one of your numbers to a U.S. number. Body: from, to, say (spoken when answered, then the call ends). Needs recorded consent for the recipient (POST /v1/consents) and voice enabled for your account.
{ "from": "+18455550100", "to": "+12125550199", "say": "Your order has shipped." }
GET/v1/calls (GET /v1/calls)
scope: calls:read
List recent calls, newest first. ?limit=1..100.
GET/v1/calls/{id} (GET /v1/calls/{id})
scope: calls:read
One call: status, duration_seconds, forwarded_to, has_voicemail.
GET/v1/calls/{id}/voicemail (GET /v1/calls/{id}/voicemail)
scope: calls:read
Redirects to a fresh link to the voicemail recording.
POST/v1/sip-trunk (POST /v1/sip-trunk)
scope: calls:write
Create your outbound SIP trunk, or replace its credentials. Returns sip_host, username, password and the X-Star72-Trunk header value once. Your phone system registers nothing: it sends each call to sip:+1NUMBER@sip.telnyx.com with digest auth and that header. Caller id must be one of your numbers; 911 always connects.
GET/v1/sip-trunk (GET /v1/sip-trunk)
scope: calls:read
Whether your trunk is set up, and its username.
Account
GET/v1/me (GET /v1/me)
The organization and scopes behind your key.
GET/v1/balance (GET /v1/balance)
scope: usage:read
Your prepaid balance, exact to the micro-dollar.
GET/v1/usage (GET /v1/usage)
scope: usage:read
Charges by kind. ?from=YYYY-MM-DD&to=YYYY-MM-DD.
Webhooks
We POST message.received, message.status, call.completed and call.voicemail, port.status_changed and port.comment events to your endpoint. A ported number becomes one of your numbers when its port reaches ported. Fetch a voicemail with GET /v1/calls/{id}/voicemail; it always returns a fresh link.
Verify the Star72-Signature header: t=<unix>,v1=<hex> where v1 is HMAC-SHA256 of "<t>.<raw body>" with your signing secret.
Every event is { "type", "created_at", "data" }, where data is the message, call or port as the API returns it. Message status: queued, sending, sent, delivered, undelivered, failed. Answer 2xx; anything else is retried with backoff.
Errors
| HTTP | Error code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 402 | insufficient_balance | Add funds to continue. |
| 400 | invalid_json | The body is not valid JSON. |
| 403 | account_not_active / account_suspended / insufficient_scope / voice_not_enabled | Account not approved or suspended, the key lacks the scope, or voice is off. |
| 404 | not_found | The resource does not exist in your account. |
| 404 | number_not_found | from is not one of your active numbers. |
| 409 | limit_reached / already_attached / brand_not_ready / port_exists / port_not_editable | Your plan limit, or a conflicting state. |
| 422 | not_portable / port_incomplete / provider_rejected | The number cannot port, details are missing (see details), or the carrier refused the request. |
| 422 | invalid_request / invalid_number / recipient_opted_out / consent_required / number_not_registered | Fix the request; the message says how. |
| 429 | rate_limited | Sending faster than your limit allows. |
| 502 | carrier_unavailable / carrier_unconfirmed | The carrier did not answer. Do not resubmit purchases. |
| 503 | provider_unavailable | The carrier is busy; retry shortly. |
Questions? support@star72.com