Skip to main content

Números extras e On Demand

Compre um número extra de WhatsApp pela API pública com uma chave de escopo do tenant. A precificação segue o modelo On Demand: você paga um preço mensal por número conectado (definido por tenant pela equipe da Pilot Status) e obtém mensagens ilimitadas em cada número.
Aba de cartão salvo na página de Planos

A aba Cartão em Planos — salve um cartão uma vez e números extras comprados via API são cobrados automaticamente (On Demand).

A cobrança roda sobre uma carteira pré-paga: primeiro os créditos, depois o cartão salvo para o que os créditos não cobrirem. Os créditos podem ser recarregados pelo painel (cartão ou PIX) ou pela API (só cartão).
Chaves de escopo de número recebem 403 — use a chave de escopo do tenant (painel Profile → API).

Cobrado na primeira conexão

Um número extra é um número de WhatsApp acima da capacidade que o seu workspace já pagou: a cota do plano, mais os números extras que você comprou, mais os que foram concedidos a você. Ele é cobrado quando o número conecta pela primeira vez — em todo plano, e por qualquer caminho de conexão: o painel, a API, o servidor MCP, um link de pareamento remoto ou o Embedded Signup da Meta.
  • Criar um número não cobra nada. Gerar um link de pareamento também não.
  • Um número que nunca conectou não conta na sua capacidade nem na sua cobrança mensal. Você paga só pelos números que foram de fato pareados.
  • Em um plano pago, a cobrança é automática. É a mesma compra de POST /v1/subscription/extra-numbers para um número: proporcional aos dias que faltam no ciclo, primeiro os créditos da carteira, o restante no cartão salvo. O plano Free nunca compra sozinho — veja Plano Free.
  • Comprar antes continua funcionando. Um número que conecta dentro da capacidade que você já pagou não é cobrado de novo.
  • Em um número não oficial, a primeira conexão é a primeira vez em que o QR Code ou o código de pareamento é concluído. Em um número oficial (Meta Cloud API), é quando o número é ativado — por exemplo, quando o Embedded Signup é concluído.
Uma recusa continua existindo na criação. Em um plano pago, quando o próximo número seria um número extra e o workspace não tem saldo na carteira para cobri-lo nem cartão salvo, criar o número — ou o link de pareamento dele — continua respondendo 402 com INSUFFICIENT_FUNDS ou PLAN_NUMBER_LIMIT_REACHED. No plano Free, um número acima da cota do plano é recusado com 402 PLAN_NUMBER_LIMIT_REACHED, qualquer que seja o saldo. O corpo não mudou: veja erros de capacidade.

Plano Free

O plano Free nunca compra um número extra sozinho. Comprar um número extra no Free converte a conta para o On Demand e transforma em pago o número que você já tinha. Isso só acontece pela compra explícita, que pede confirmação: o diálogo de compra do painel, ou POST /v1/subscription/extra-numbers com confirm: true. Um workspace no Free nunca é convertido nem cobrado por conectar um número.
  • Criar um número acima da cota do plano é recusado com 402 PLAN_NUMBER_LIMIT_REACHED, mesmo quando o workspace tem saldo na carteira ou cartão salvo. Nos planos pagos, havendo saldo ou cartão a criação passa, e o extra é cobrado na primeira conexão.
  • Parear um número que nunca conectou também é recusado quando a cota do plano já está cheia. GET /v1/numbers/{id}/connect, o passo de conexão de um link de pareamento remoto e POST /v1/numbers/remote-pairing para um telefone que já existe respondem 402 PLAN_NUMBER_LIMIT_REACHED. Reparear um número que já tinha conectado nunca é recusado.
  • Se um segundo número conecta mesmo assim — dois pareamentos iniciados ao mesmo tempo — nada é cobrado e o plano não muda. O número continua conectado com o envio bloqueado (409 NUMBER_BLOCKED, reason: "extra_charge_unpaid"), e POST /v1/numbers/{id}/pay responde 402 PLAN_NUMBER_LIMIT_REACHED para ele. Uma recarga ou um cartão salvo não removem esse bloqueio.
Para usar o segundo número, compre o número extra por conta própria ou suba de plano. Um número que estava bloqueado é então liberado em até 15 minutos — ou na hora, se você chamar POST /v1/numbers/{id}/pay, que responde not_required.

Quando a cobrança é recusada

Se o cartão é recusado ou não há saldo, o número continua conectado, mas o envio por ele fica bloqueado até o pagamento:
  • POST /v1/messages/send responde 409 NUMBER_BLOCKED com reason: "extra_charge_unpaid".
  • Uma mensagem que já estava na fila falha, e aparece com NUMBER_BLOCKED nos logs.
  • GET /v1/numbers e GET /v1/numbers/{id} mostram extraChargeUnpaidSince no número.
Só esse número fica bloqueado. Os seus outros números continuam enviando, e o número bloqueado continua recebendo.
Uma cobrança que não pôde ser concluída não é uma recusa. Quando o processador de pagamento está fora do ar, nada é bloqueado: o número continua enviando, e a cobrança é retentada automaticamente a cada 15 minutos.

O que remove o bloqueio

Se a renovação do próprio plano está em atraso, a recarga ou o cartão salvo pagam primeiro a renovação. No plano Free nenhuma das quatro vale: veja Plano Free. O bloqueio também sai sozinho, em até 15 minutos, quando a sua capacidade passa a cobrir o número — por exemplo, depois que você compra um número extra com POST /v1/subscription/extra-numbers.

Depois de 7 dias sem pagamento

  • Um número conectado por QR Code que fica 7 dias sem pagamento é desconectado: o aparelho é deslogado e o número deixa de contar na sua capacidade. Um lembrete é enviado 2 dias antes, e o extraChargeDisconnectAt do número diz quando. Conectá-lo de novo é uma nova primeira conexão, cobrada de novo.
  • Um número da Meta Cloud API nunca é desconectado. Ele fica bloqueado para envio até o pagamento.

GET /v1/subscription/extra-numbers — Prévia (dry-run)

Retorna o custo e se a compra converte o plano. proratedTotal é o que você paga agora; monthlyTotalBRL é o que passa a ser cobrado a cada ciclo:
Você só paga pelos dias que faltam no ciclo. A primeira cobrança é proporcional: proratedUnitPrice = unitPriceMonthly × (remainingDays / totalDaysInCycle). No exemplo acima falta metade do ciclo, então são 2 números × R$14,95 = R$29,90 agora, e R$59,80/mês a partir do próximo ciclo.
fundingSource diz como a cobrança será coberta — credits (a carteira cobre todo o proratedTotal), credits+card (parte em créditos, o resto no cartão salvo), card ou none. none significa que a carteira não cobre o proratedTotal e não há cartão salvo (hasSavedCard: false): canFund vem false, e o POST responde 402 INSUFFICIENT_FUNDS sem cobrar nada. Ele não devolve link de checkout — recarregue a carteira ou salve um cartão antes (POST /v1/billing/checkout, abaixo).

POST /v1/subscription/extra-numbers — Comprar

Comportamento:
  • Conversão Free → On Demand. Se você está no plano Free, comprar um número extra move você para o On Demand: seu número atual (antes gratuito, limitado a 200 mensagens) se torna um número pago e ilimitado e o novo número também é cobrado. Portanto, comprar 1 extra estando no Free cobra você por 2 números. Como isso altera o que você paga, o POST retorna 409 CONFIRMATION_REQUIRED (com a prévia) a menos que você envie confirm: true.
  • Já no On Demand / pago. Comprar incrementa sua contagem de números; as mensagens permanecem ilimitadas.
  • Pagamento. O custo proporcional é cobrado na hora: primeiro da sua carteira pré-paga, e o que sobrar vai no cartão salvo (painel Plans → Cartão, ou POST /v1/billing/checkout com add_card). Uma compra bem-sucedida responde { "success": true, "charged": true, … } — creditsUsed e cardCharged mostram como a cobrança foi dividida, e paidExtraNumbers já inclui a vaga nova, então você pode criar o número com POST /v1/numbers em seguida. Esta rota nunca devolve link de checkout (não existe checkoutUrl, e charged nunca vem false): sem créditos suficientes e sem cartão salvo, ela retorna 402 INSUFFICIENT_FUNDS — nada é cobrado, e os créditos que ela chegou a usar são devolvidos. Recarregue ou salve um cartão e tente de novo.
  • A recorrência mensal sai da carteira, não de uma assinatura à parte. A cada ciclo, paidExtraNumbers × preço unitário é debitado dos créditos, com o cartão salvo como reserva. Recarregue com POST /v1/billing/checkout (wallet_topup, só cartão) ou pelo painel, onde o PIX também está disponível — dá para rodar On Demand só com créditos de PIX recarregados pelo painel, sem nunca salvar um cartão.
Mantenha créditos ou um cartão salvo disponíveis. Se a cobrança de um ciclo não puder ser paga, você tem 2 dias para adicionar créditos ou atualizar o cartão antes que seus números parem de enviar (o recebimento continua). Adicionar saldo reativa na hora.

Quando o pagamento não passa

POST /v1/subscription/extra-numbers distingue três falhas, e elas não dividem a mesma solução. Decida pelo code, nunca pelo status HTTP: duas das três são 402. Todos esses corpos trazem proratedTotal e currency, então dá para mostrar o valor que foi tentado. O INSUFFICIENT_FUNDS traz também walletReason, que nesta rota é sempre NO_CARD. Este é o corpo de INSUFFICIENT_FUNDS quando os créditos não cobrem a cobrança e não há cartão salvo (o error é uma mensagem legível, em português — decida pelo code):
Nos três casos o número extra não foi adicionado. Seu paidExtraNumbers continua igual e nenhum slot foi liberado — toda escrita acontece depois de uma cobrança bem-sucedida.
502 PAYMENT_PROVIDER_ERROR não quer dizer “nada foi cobrado”. Quer dizer que a requisição ao processador falhou ou que a resposta dele se perdeu — a cobrança pode ter se concluído do lado dele sem que a gente ficasse sabendo. A contagem não mudou, então repetir às cegas pode pagar duas vezes. Confira o extrato primeiro.

O campo declineCode

O PAYMENT_FAILED às vezes traz declineCode. Quando vem, é o próprio código de recusa da bandeira, literal (insufficient_funds, expired_card, card_not_supported, …) — pode ser mostrado ao titular do cartão ou usado para decidir. ⚠️ A ausência dele não diz nada. Um PAYMENT_FAILED sem declineCode é um pagamento que não passou por um motivo que não conseguimos atribuir ao cartão — um rate limit na requisição, uma chave de integração expirada, um payment intent que ficou num estado diferente de sucesso. Não inventamos um código para preencher o campo. Portanto:
  • Decida pelo code: "PAYMENT_FAILED". Ele está sempre presente e sempre significa a mesma coisa.
  • Use o declineCode só para refinar a mensagem quando ele existir.
  • Nunca leia “sem declineCode” como “não foi recusa” — e nunca como “o cartão está bom”.
O declineCode nunca aparece no INSUFFICIENT_FUNDS nem no PAYMENT_PROVIDER_ERROR. Nenhum dos dois é recusa de cartão.

A capacidade paga é um direito de 30 dias

Ao comprar números extras, essa capacidade é sua durante todo o ciclo de 30 dias. Duas consequências:
  • Deletar um número não reduz sua capacidade paga. DELETE /v1/numbers/<id> libera o slot para você reconectar outro número no mesmo ciclo — sua contagem paga e sua cobrança seguem iguais até o fim do ciclo.
  • Reduzir a capacidade é uma ação explícita e agendada. Você decide baixar a quantidade de slots pagos; a mudança vale no próximo ciclo, quando a cobrança cai para o novo valor. Nada é estornado do ciclo em andamento.

No Pay As You Go funciona diferente

No Pay As You Go não existe capacidade para comprar de volta nem para agendar redução — você paga pelos números que tem conectados:
  • O número é cobrado quando conecta pela primeira vez, prorata pelos dias restantes do ciclo — a mesma regra de todos os planos, descrita acima. Para número não-oficial isso é a primeira conexão; para número oficial (Meta), é quando o Embedded Signup é concluído.
  • O slot que você pagou vale pelo ciclo. Deletar um número e conectar outro no lugar não cobra de novo.
  • O ciclo seguinte cobra só o que continuar conectado. Nada se acumula do ciclo anterior, então não há o que agendar para baixo — veja o 409 NOT_APPLICABLE_FOR_PLAN no endpoint abaixo.
  • Um pareamento que você nunca concluiu não é um número e não é cobrado. A linha “Aguardando conexão”, que aparece enquanto o seu usuário final está no diálogo do Facebook, fica de fora da cobrança e da sua capacidade, e é removida automaticamente 24h depois de criada.

DELETE /v1/subscription/extra-numbers — Agendar uma redução

Agenda a redução de quantity slot(s) pago(s) para o próximo ciclo:
Comportamento:
  • Agendado, não imediato. Este ciclo mantém a capacidade atual (sem estorno); a redução vale a partir do próximo rollover, quando os meses futuros cobram menos.
  • Editável. Chamar de novo sobrescreve o alvo agendado. Cancele com DELETE …/schedule (abaixo).
  • Não pode ficar abaixo dos números conectados. Se o alvo deixaria o limite abaixo dos números que você ainda tem conectados, retorna 409 NUMBER_LIMIT_EXCEEDED ({ currentNumberCount, maxNumbers }) — delete um número antes.
  • Não existe no Pay As You Go. Retorna 409 NOT_APPLICABLE_FOR_PLAN. Não há capacidade paga a devolver: o Pay As You Go cobra pelos números que você tem conectados, e o próximo ciclo já cobra apenas o que continuar conectado. Para pagar menos, delete o número — não é preciso agendar nada.
  • quantity tem default 1 e é limitado para nunca ficar abaixo de 0 extras pagos. Chaves de escopo de número recebem 403.
Resposta — paidExtraNumbers continua no valor do ciclo atual; scheduledPaidExtraNumbers é o alvo que passa a valer no próximo ciclo:

DELETE /v1/subscription/extra-numbers/schedule — Cancelar a redução agendada

Cancela uma redução pendente para que o próximo ciclo mantenha sua capacidade paga atual. No-op se nada estiver agendado.

POST /v1/billing/checkout — Recarga de carteira / adicionar um cartão

Gera uma URL de checkout hospedada para ações de cobrança:
  • purpose: "wallet_topup" — recarrega a carteira pré-paga. Só cartão: o checkout hospedado do Stripe não oferece PIX — recarga por PIX existe só pelo painel (contas em BRL). Exige amount: um número positivo, na moeda da sua conta (a da carteira, BRL ou USD — não dá para escolher por requisição). Ausente, zero, negativo ou não numérico retorna 400 { "error": "amount must be a positive number", "code": "INVALID_AMOUNT" }.
  • purpose: "add_card" — salva um cartão para futuras cobranças On Demand.
A resposta contém uma url para abrir no navegador. No wallet_topup ela traz também orderId, amount e currency: