Skip to main content

Extra numbers & On Demand

Buy an extra WhatsApp number via the public API with a tenant-scoped key. Pricing follows the On Demand model: you pay a monthly price per connected number (set per-tenant by the Pilot Status team) and get unlimited messages on every number.
Saved card tab in the Plans page

The Card tab in Plans — save a card once and extra numbers bought via the API bill automatically (On Demand).

Billing runs off a prepaid wallet: credits first, saved card for whatever the credits do not cover. Credits can be topped up by card or PIX.
Number-scoped keys get 403 — use the tenant-scoped key (dashboard Profile → API).

GET /v1/subscription/extra-numbers — Preview (dry-run)

Returns the cost and whether buying converts the plan. proratedTotal is what you are charged now; monthlyTotalBRL is what recurs every cycle after that:
You only pay for the days left in the cycle. The first charge is prorated: proratedUnitPrice = unitPriceMonthly × (remainingDays / totalDaysInCycle). In the example above, half the cycle is left, so 2 numbers × R$14,95 = R$29,90 now, and R$59,80/month from the next cycle on.
fundingSource tells you how the charge will be covered — credits, card, credits+card, or none. When it is none, canFund is false and the POST returns a checkoutUrl instead of charging.

POST /v1/subscription/extra-numbers — Buy

Behaviour:
  • Free → On Demand conversion. If you are on the Free plan, buying an extra number moves you to On Demand: your current (formerly free, 200-message-limited) number becomes a paid, unlimited number and the new number is billed too. So buying 1 extra while Free bills you for 2 numbers. Because this changes what you pay, the POST returns 409 CONFIRMATION_REQUIRED (with the preview) unless you send confirm: true.
  • Already On Demand / paid. Buying increments your number count; messages stay unlimited.
  • Payment. The prorated cost is taken from your prepaid wallet first, then any remainder from your saved card (dashboard Plans → Cartão). With neither, the response is { "success": true, "charged": false, "checkoutUrl": "https://…" } — open it to pay the prorated amount; the card you use there is saved for the recurring charge. Once paid, the slot unlocks (webhook) and you can create the number with POST /v1/numbers.
  • The monthly recurrence comes out of the wallet, not a separate subscription. Each cycle, paidExtraNumbers × unit price is debited from your credits, falling back to your saved card. Top the wallet up with POST /v1/billing/checkout (wallet_topup), which accepts PIX — so you can run On Demand on PIX credits and never save a card.
Keep either credits or a saved card available. If a cycle’s charge cannot be funded, you get 2 days to add credits or update the card before all your numbers stop sending (inbound keeps working). Adding funds reactivates immediately.
To lower your paid capacity, see the scheduled-reduction section below.

When the payment does not go through

POST /v1/subscription/extra-numbers distinguishes three failures, and they do not share a remedy. Branch on code, never on the HTTP status: two of the three are 402. Every one of these bodies carries proratedTotal and currency, so you can show the amount that was attempted. INSUFFICIENT_FUNDS also carries walletReason (NO_CARD or INSUFFICIENT_CREDITS).
In all three cases the extra number was not added. Your paidExtraNumbers is unchanged and no slot was unlocked — every write is downstream of a successful charge.
502 PAYMENT_PROVIDER_ERROR does not mean “nothing was charged”. It means the request to the processor failed or its response was lost — the charge may have completed on the processor’s side while we never heard back. The count did not move, so a blind retry can pay twice. Check your statement first.

The declineCode field

PAYMENT_FAILED sometimes carries declineCode. When present, it is the card network’s own decline code, verbatim (insufficient_funds, expired_card, card_not_supported, …) — safe to show to the cardholder or to branch on. ⚠️ Its absence tells you nothing. A PAYMENT_FAILED with no declineCode is a payment that did not go through for a reason we could not attribute to the card — a rate limit on the request, an expired integration key, a payment intent left in a non-succeeded state. We do not invent a code to fill the field. So:
  • Treat code: "PAYMENT_FAILED" as the decision. It is always present and always means the same thing.
  • Use declineCode only to refine the message when it is there.
  • Never read “no declineCode” as “not a decline” — and never as “the card is fine”.
declineCode never appears on INSUFFICIENT_FUNDS or PAYMENT_PROVIDER_ERROR. Neither is a card decline.
When you buy extra numbers, that capacity is yours for the full 30-day cycle. Two consequences:
  • Deleting a number does not reduce your paid capacity. DELETE /v1/numbers/<id> frees the slot so you can reconnect a different number in the same cycle — your paid count and your bill stay the same until the cycle ends.
  • Reducing capacity is an explicit, scheduled action. You decide to lower the number of paid slots; the change takes effect at the next cycle, when billing drops to the new amount. Nothing is refunded for the running cycle.

Pay As You Go works differently

On Pay As You Go there is no capacity to buy back or schedule away — you pay for the numbers you have connected:
  • A number is charged when it becomes usable, prorated for the days left in the cycle. For an unofficial number that is the first connection; for an official (Meta) number it is when the Embedded Signup finishes.
  • The slot you paid for lasts the cycle. Deleting a number and connecting another one in its place does not charge again.
  • The next cycle bills only what is still connected. Nothing accumulates from the previous cycle, so there is nothing to schedule down — see the 409 NOT_APPLICABLE_FOR_PLAN on the endpoint below.
  • A pairing you never finished is not a number and is not billed. The “awaiting connection” row that appears while your end user is inside the Facebook dialog is excluded from billing and from your capacity, and it is removed automatically 24h after it was created.

DELETE /v1/subscription/extra-numbers — Schedule a reduction

Schedules a reduction of quantity paid slot(s) for the next cycle:
Behaviour:
  • Scheduled, not immediate. This cycle keeps the current capacity (no refund); the reduction applies at the next rollover, when future months bill less.
  • Editable. Calling again overwrites the scheduled target. Cancel it with DELETE …/schedule (below).
  • Cannot drop below connected numbers. If the target would leave your limit under the numbers you still have connected, it returns 409 NUMBER_LIMIT_EXCEEDED ({ currentNumberCount, maxNumbers }) — delete a number first.
  • Not available on Pay As You Go. Returns 409 NOT_APPLICABLE_FOR_PLAN. There is no paid capacity to give back: Pay As You Go bills the numbers you have connected, and the next cycle already charges only what stayed connected. To pay less, delete the number — scheduling nothing is required.
  • quantity defaults to 1 and is clamped so you never go below 0 paid extras. Number-scoped keys get 403.
Response — paidExtraNumbers stays at the current (this-cycle) value; scheduledPaidExtraNumbers is the target that takes effect next cycle:

DELETE /v1/subscription/extra-numbers/schedule — Cancel the scheduled reduction

Cancels a pending reduction so the next cycle keeps your current paid capacity. No-op if nothing is scheduled.

POST /v1/billing/checkout — Wallet top-up / add a card

Generates a hosted checkout URL for billing actions:
  • purpose: "wallet_topup" — top up the prepaid wallet.
  • purpose: "add_card" — save a card for future On Demand charges.
The response contains a checkoutUrl to open in the browser.