Skip to main content

Phone lines

A phone line is a Brazilian landline number that you rent from Pilot Status to activate a WhatsApp Business account on it — no SIM card involved. When WhatsApp verifies the number, it calls the line; we capture that call, transcribe it and hand you the 6-digit code through the API, the dashboard and a webhook. Lines are receive-only: they do not place calls or send SMS, and a landline does not receive SMS — the code always arrives by phone call. A line is a product of its own: buying one does not create or connect a WhatsApp number in your workspace.
Every /v1/phone-lines endpoint requires a tenant-scoped key (dashboard Profile → API). A line belongs to the workspace, not to a WhatsApp number, so a number-scoped key gets 403 NUMBER_SCOPE_NOT_ALLOWED.

Endpoints

Events (phone_line.purchased, phone_line.code_received, …) are delivered to phone-line webhooks, a registry separate from your number webhooks — see Phone line webhooks.

Credentials and permissions

A classic ps_ key has no user and therefore no role: its authority is its scope. An OAuth or MCP token carries a user, and the token never reaches further than that user’s current role in the workspace: A role that lacks the permission gets 403 PERMISSION_DENIED.
The activation code is a credential. Whoever holds it can register a WhatsApp account on the line. That is why reading lines and codes stops at Admin, and why every response that can carry a code is sent with Cache-Control: no-store. Keep the tenant key on your backend.

Before the first purchase: identity verification

Renting a phone line requires a verified account holder (CNPJ or CPF, plus contact confirmation). Verification is done in the dashboard only — Phone lines (/linhas) → Buy phone lines — and is asked for once, on the first purchase. There is no verification endpoint in the public API. Through the API, an unverified workspace can search numbers and try to buy; the purchase then answers: Changes of verification state are also delivered as the phone_line.verification_updated webhook event.

Pricing and payment

  • R$ 33,90 per line per month for BRL workspaces, US$ 33.90 for USD workspaces. The price is frozen on each line at purchase: price and currency on the line are what every renewal of that line charges.
  • The first month is charged in full at purchase — no proration.
  • Each line has its own cycle, anchored on its purchase date. It renews on the same day of the following month; when that day does not exist, on the last day of the month, and the shorter day then sticks: bought on Jan 31 → renews Feb 28 (or 29) → Mar 28 → Apr 28. Dates are computed in UTC. currentPeriodEnd on the line is the next renewal.
  • Payment comes from the prepaid wallet first, and whatever the credits do not cover is charged to your saved card. If the card step fails, the credits already taken are put back. Top the wallet up in the dashboard (card or PIX), or through the API with POST /v1/billing/checkout — wallet_topup (card only) or add_card to save a card.
  • Lines are available on every plan, including Free, with no limit on how many a workspace holds. A single POST buys at most 10.
  • Lines share the wallet with the WhatsApp product. The dashboard shows the statement grouped by product.

Line lifecycle

Timing:
  • Renewal is attempted when currentPeriodEnd is reached. Billing runs once an hour, so the charge — and every transition below — happens within about an hour after its deadline.
  • A failed renewal moves the line to PAYMENT_PENDING and is retried every 6 hours. Adding credits or a card is picked up at the next retry; the dashboard’s Pay for all button retries at once.
  • 2 days after the failure the line is SUSPENDED; 1 day later it is RETURNED. From the first failed charge to losing the number: about 3 days.
  • A late payment keeps the anniversary. When a renewal finally succeeds — even while SUSPENDED — the line goes back to ACTIVE and the new period runs from the original renewal date, not from the payment date.
  • Each step sends a notice and a webhook event: phone_line.payment_failed (once per renewal, on its first failed attempt), phone_line.suspended and phone_line.returned. A successful renewal sends phone_line.renewed.
Returning a number is irreversible. The number goes back to the carrier’s pool and stops being yours; nothing undoes a return, and there is no guarantee you can ever get that number again. The WhatsApp account activated on it stays tied to that number: whoever rents it next can request a verification code for it. Keep credits or a saved card available.

Cancelling

DELETE /v1/phone-lines/{id} depends on the state — see Cancel a line:
  • ACTIVE — no refund. The line stays ACTIVE and usable (codes included) until currentPeriodEnd, with cancelAtPeriodEnd: true; it is not renewed and becomes CANCELED at the end of the period. Undoing a scheduled cancellation is dashboard-only.
  • PAYMENT_PENDING / SUSPENDED — there is no paid period left, so the line is returned immediately (RETURNED).
  • RETURNED / CANCELED — 409 NOT_CANCELABLE.

The activation-code flow

1

Get the line ready

POST /v1/phone-lines/{id}/activations (no body). The line is reset at the carrier — so a recording of an earlier call can never be read as this one’s code — and a code request opens in WAITING, with a 5-minute window (pollDeadline). Call this before you ask WhatsApp to call.
2

Ask WhatsApp to call the line

Register the number in WhatsApp Business and, when WhatsApp asks how to receive the code, choose the phone call option (“Call me”). The line is a landline: waiting for an SMS only burns the window.
3

We capture and transcribe the call

We check the line about every 15 seconds during the window. When the call’s recording appears we store it, transcribe it and extract the 6-digit code.
4

Read the code

Poll GET /v1/phone-lines/activations/{activationId} every few seconds until status is TRANSCRIBED (or another final state), or subscribe to the phone_line.code_received webhook. Type code into WhatsApp.
Only one request per line can be waiting at a time (409 ACTIVATION_IN_PROGRESS). There is no limit on how many codes a line requests over its life; each request that opens increments the line’s activationCount. Suspended and finished lines cannot request codes (409 LINE_NOT_ACTIVE). The call recording can be listened to in the dashboard only. Full detail, statuses and edge cases: Activation codes.

Idempotency

POST /v1/phone-lines moves money, so it requires an Idempotency-Key header. A retry with the same key never charges twice for the same number within one hour: the item comes back ok: false with REQUEST_ALREADY_PROCESSED. The exact rules — including what a replay does and does not tell you — are in Buy phone lines → Idempotency. The other endpoints need no key: reads are safe, DELETE on an ACTIVE line is a no-op the second time, and a second POST …/activations while one is waiting answers 409 ACTIVATION_IN_PROGRESS instead of opening another.

Limits

  • 1 to 10 numbers per purchase request, with no duplicates. No limit on lines per workspace.
  • One waiting code request per line.
  • Code history: limit 1–100 per call (default 20).
  • Calls that reach our carrier — searching, buying, requesting a code, cancelling an unpaid line — draw on a request budget that is shared by the whole platform. When it runs out they answer 503 SUPPLIER_UNAVAILABLE (“Try again in a few minutes”), or the per-number SUPPLIER_UNAVAILABLE inside a purchase. Search once per area code and reuse the result instead of polling it.
  • There is no other rate limit specific to these endpoints.

Errors

Every error body has the same envelope. The error string carries an English sentence and a Portuguese one separated by " | "; branch on code, never on the text:
Validation errors raised by the routes themselves may add a details object (unknownFields, invalidNumbers, maxLength). The authentication and scope errors shared by the whole API are the exception to the bilingual rule: a 401 carries only error (no code), and the 403s for scope and role carry an English-only error. Other errors shared by the whole API can also answer here — for example 403 WORKSPACE_ARCHIVED for an archived workspace, or 404 NUMBER_NOT_FOUND when an x-whatsapp-number-id header names no number of your account.

Per-number results of a purchase

POST /v1/phone-lines answers 200 with one result per number. A failed item carries code and a bilingual error, but no HTTP status of its own — the request as a whole was processed: A refund after a charge always goes to the wallet, including any part that was paid by card. See Buy phone lines for the full semantics.
The route-level validation errors (INVALID_QUERY, INVALID_LIMIT, IDEMPOTENCY_KEY_*, INVALID_BODY, UNKNOWN_FIELDS, INVALID_NUMBERS) build their message from the request — it names the offending field or numbers — and follow the same English | Portuguese pattern.

Dashboard-only

These have no public API endpoint; use the dashboard (Phone lines, /linhas):
  • Identity verification (document upload and contact code).
  • Listening to the call recording of a code request.
  • Undoing a scheduled cancellation.
  • Pay for all and Choose which lines to keep for unpaid lines.
  • The wallet statement grouped by product.
  • Creating and managing phone-line webhooks and reading their delivery log.