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

HTTPError codeMeaning
401unauthorizedMissing or invalid API key.
402insufficient_balanceAdd funds to continue.
400invalid_jsonThe body is not valid JSON.
403account_not_active / account_suspended / insufficient_scope / voice_not_enabledAccount not approved or suspended, the key lacks the scope, or voice is off.
404not_foundThe resource does not exist in your account.
404number_not_foundfrom is not one of your active numbers.
409limit_reached / already_attached / brand_not_ready / port_exists / port_not_editableYour plan limit, or a conflicting state.
422not_portable / port_incomplete / provider_rejectedThe number cannot port, details are missing (see details), or the carrier refused the request.
422invalid_request / invalid_number / recipient_opted_out / consent_required / number_not_registeredFix the request; the message says how.
429rate_limitedSending faster than your limit allows.
502carrier_unavailable / carrier_unconfirmedThe carrier did not answer. Do not resubmit purchases.
503provider_unavailableThe carrier is busy; retry shortly.

Questions? support@star72.com