Buy phone lines
POST /v1/phone-lines buys 1 to 10 numbers taken from GET /v1/phone-lines/available. Each number becomes a line of your workspace, ACTIVE and paid for its first month.
Requires a tenant-scoped key. With an OAuth / MCP token, only the workspace Owner may buy (
phone_lines:purchase) — 403 PERMISSION_DENIED otherwise. The account holder must be verified first, in the dashboard — see identity verification.Endpoint
POST https://pilotstatus.com.br/v1/phone-lines
Headers
string
required
Any string up to 255 characters with no control characters — a UUID per purchase is the usual choice. Missing, empty or whitespace-only → 400
IDEMPOTENCY_KEY_REQUIRED; too long or with a control character → 400 IDEMPOTENCY_KEY_INVALID. See Idempotency.Request body
string[]
required
1 to 10 distinct numbers, each exactly as
GET /v1/phone-lines/available returned it in number: E.164 digits without + — 55, a two-digit area code starting 1–9, then 8 digits (551148637200). Numbers from different area codes can go in the same request.numbers is the only accepted field. Anything else is refused with 400 UNKNOWN_FIELDS, naming the fields — in particular, the idempotency key is not a body field (the dashboard’s requestId does not exist here).
Example
Response: 200 with one result per number
The numbers are independent. Each one is charged and acquired on its own, in the order you sent them, and one that fails does not undo the others. So no single status describes the request: it answers 200 when it was processed, and the outcome of each number is in results[i].ok.
object[]
One entry per number, in the order sent.
string
The number, as you sent it.
boolean
true when the line was bought by this request.string
Only when
ok: true. The new line’s id, for GET /v1/phone-lines/{id} and the activation-code endpoints.string
Only when
ok: false. Why this number failed — see the table below.string
Only when
ok: false. Bilingual message (English | Portuguese) for that code.Order of operations, per number
- Reserve the number under your
Idempotency-Key(see below). Already reserved →REQUEST_ALREADY_PROCESSED. - Check that no line holds it — in any workspace, yours included — and that no other purchase of it is in flight → otherwise
NUMBER_UNAVAILABLE, nothing charged. - Charge the full monthly price: wallet credits first, the remainder on the saved card. If the card step fails, the credits already taken are put back →
PAYMENT_FAILED. - Acquire the number at the carrier. If that fails, the full price is refunded to your wallet — including any part that was paid by card — →
NUMBER_UNAVAILABLEorSUPPLIER_UNAVAILABLE. - Create the line:
status: "ACTIVE",currentPeriodEndone month ahead, and thephone_line.purchasedwebhook event.
PAYMENT_FAILED. Fund the wallet first — in the dashboard (card or PIX), or with POST /v1/billing/checkout (wallet_topup, card only) — or save a card (add_card).
Whole-request errors
Anything other than200 means no number was attempted by this request, with one exception — 500, see below. Checks run in this order:
Idempotency
TheIdempotency-Key header is mandatory because a client that times out on this request has no other way to know whether money moved. What it guarantees, exactly:
- The key is tracked per number, for one hour. Before anything else, each number is recorded under (your workspace, the key, the number). For the next hour, sending the same key again with that number charges nothing and buys nothing: the item answers
ok: false,REQUEST_ALREADY_PROCESSED. - A replay does not return the original result.
REQUEST_ALREADY_PROCESSEDcomes back whether the first attempt bought the number or failed. To know which, callGET /v1/phone-lines: a number that was bought is in your list. - A number that failed is locked under that key for the hour. After a
PAYMENT_FAILEDyou top up and retry — with a new key, or the retry answersREQUEST_ALREADY_PROCESSED. - After the hour the key is forgotten. A number you bought then answers
NUMBER_UNAVAILABLE(it is yours — there is no second charge); a number that had failed is attempted again. To retry a failed number, always use a new key. - Only the numbers are protected, not the request. Sending the same key with a different number buys that number.
- A request refused as a whole records nothing. After any
400or403above, fix the request and resend it with the same key. - Concurrent duplicates. If two deliveries of the same request are in flight together, each number proceeds in one of them; the other gets
REQUEST_ALREADY_PROCESSEDfor it. - Scoped to your workspace. The same key sent by another workspace never collides with yours.
The record is kept in a cache. If that cache is unavailable, the protection is skipped rather than blocking purchases — two deliveries of the same request that land during such an outage could both charge. Send each purchase once and retry only on a timeout, a
5xx or a network error.Recommended retry pattern
1
One key per purchase
Generate a UUID for each purchase the user makes and send it as
Idempotency-Key. Use a generous client timeout: numbers are processed one after another.2
On timeout, 5xx or network error
Resend the same body with the same key. Numbers already handled come back
REQUEST_ALREADY_PROCESSED; numbers the first attempt never reached are processed now.3
Reconcile
Call
GET /v1/phone-lines and match by number to learn which lines you now hold.4
Retry failures with a new key
For numbers that failed (
PAYMENT_FAILED, SUPPLIER_UNAVAILABLE), fix the cause and send them with a new key.After the purchase
- The line is
ACTIVEuntilcurrentPeriodEnd— one month after the purchase — and then renews monthly. See the line lifecycle. - Request the WhatsApp activation code with
POST /v1/phone-lines/{id}/activations.