Skip to main content

Create, connect and delete numbers

Create a WhatsApp number via the public API, connect it (QR code or pairing code), and delete it when no longer needed.
Use a tenant-scoped API key (recommended for SaaS platforms managing multiple numbers). A number-scoped key also works — it stays bound to the number it already covers. Authenticate with x-api-key: ps_....

POST /v1/numbers — Create a number

string
required
Display name for the number.
string
required
The phone number in E.164 format (e.g. +5511999999999).
Retired — accepted but does nothing. Sending it is not an error, and no API key is touched: linkedApiKeyId in the response is always null. It used to re-point a number-scoped key at the number you had just created, silently unscoping it from the number it was bound to.
The response includes qrcodeBase64 and pairingCode (WhatsApp letter pairing code; may be null if the provider does not return one). Show the QR code to the person who owns the phone, or let them type the pairing code.
Call these endpoints only from your backend — never expose your ps_ key in the browser.

Meta (official Cloud API) numbers

POST /v1/numbers creates an unofficial (QR-paired) number. For official Meta Cloud API numbers, use one of the Meta flows instead:
  • POST /v1/numbers/meta — direct BYO-WABA: bring your own WABA credentials, or
  • Hosted Meta Embedded Signup via a remote pairing link with metaFlow.

POST /v1/numbers/meta — bring your own WABA

Provisions an official Meta Cloud API number from WABA credentials you already own. This flow is for platforms that already control the WABA and hold a system-user token. If you don’t have these credentials, use the hosted Embedded Signup via a remote pairing link with provider=META instead. Requires a tenant-scoped API key.
string
required
Display name for the number (1–60 characters).
string
required
The phone number as digits, with an optional leading +.
string
required
The Meta phone number ID of the WABA phone.
string
required
The WhatsApp Business Account (WABA) ID.
string
required
A system-user access token with access to the WABA.
string
required
The app secret of your Meta app.
string
required
The ID of your Meta app.
boolean
default:"false"
When true, Pilot Status subscribes your WABA to its own webhook callback (setting Meta’s override_callback_uri plus a per-number verify token), so inbound messages reach Pilot automatically — you never paste a webhook URL into your Meta App Dashboard. The response then returns webhook.routedToPilot: true.Default false. The override is a single per-WABA value, so enabling it redirects every number on that WABA away from whatever callback your app currently uses (for example your own backend, or an existing Chatwoot). Only set it when you want Pilot Status to own this WABA’s webhook delivery.
Retired — accepted but does nothing, exactly as on POST /v1/numbers above. linkedApiKeyId in the response is always null.
Errors:
  • 400 — validation error (missing or invalid body fields).
  • 402 PLAN_NUMBER_LIMIT_REACHED — your plan’s own number allowance is full and you never bought an extra number. Adding credits does not clear it: free a slot or move up a plan.
  • 402 INSUFFICIENT_FUNDS — the number would be a paid extra and there is neither wallet balance nor a saved card.
  • 403 NUMBERS_GRANT_NOT_ALLOWED — per-number OAuth grants cannot provision numbers.
  • 409 — “Number already exists”.

Capacity errors and what fixes them

Both provisioning routes on this page (POST /v1/numbers and POST /v1/numbers/meta) check capacity before the number row is written, and both refuse with 402. The status alone does not tell you what to do — the error field does. On these two routes the discriminator arrives in error only; POST /v1/numbers/remote-pairing sends the same values in both error and code. The body carries the numbers behind the refusal, so you never have to guess:
Changed. A plan whose allowance was simply full used to answer INSUFFICIENT_FUNDS as well — so an account with one number on a one-number plan was told it had no money, and went looking for a balance that was never the problem. The status is unchanged (402); what is new is the value in error, and the fields alongside it.

Webhook delivery (inbound)

Sending works the moment the number is created — it does not depend on the webhook. For Pilot Status to also receive inbound messages, Meta must deliver this WABA’s webhooks to Pilot. The response carries a webhook object for exactly this:
Two ways to wire it:
  • Automatic — pass routeWebhooksToPilot: true in the request. Pilot subscribes the WABA for you and the response returns webhook.routedToPilot: true; nothing else to configure.
  • Manual — in your Meta App Dashboard, set the WhatsApp product’s Callback URL to webhook.url and the Verify token to webhook.verifyToken.
The hosted metaFlow: "credentials" connect page (see remote pairing) exposes the same choice as a checkbox.

GET /v1/numbers//connect — Regenerate the QR code

Generates a new QR code and pairing code when the instance is not OPEN.
  • Response includes fresh qrcodeBase64 and pairingCode.
  • If the number is already connected (OPEN), the endpoint returns 409 — there is nothing to pair.
  • Not applicable to Meta numbers (they do not use QR pairing).
  • Reflects the same connection state as the Numbers page in the dashboard.

DELETE /v1/numbers/ — Remove a number

Removes the Pilot Status record and attempts to end or remove the associated WhatsApp session when applicable (HTTP 404 from cleanup steps is ignored).
  • number.created — fires when the instance is created.
  • number.connected — fires when the customer completes the QR / pairing connection (state OPEN).
  • number.removed — fires on deletion.
See the webhook event reference.