> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pilotstatus.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Comprar número extra (On Demand)

> Compra N número(s) extra(s). Requer key escopada a TENANT. Tenant Grátis é convertido para On Demand (cobra o número grátis convertido + o novo) — exige confirm:true, senão retorna 409 CONFIRMATION_REQUIRED com a prévia. Cobra o valor proporcional na hora — primeiro os créditos da carteira, o restante no cartão salvo (Planos → Cartão). Nunca devolve link de checkout: sem créditos suficientes e sem cartão salvo, retorna 402 INSUFFICIENT_FUNDS e não cobra nada; cartão recusado é 402 PAYMENT_FAILED; falha no processador é 502 PAYMENT_PROVIDER_ERROR. Efeito real: cobra de verdade. Só Stripe.

**Requer uma chave com escopo de tenant.**



## OpenAPI

````yaml openapi.pt.json POST /v1/subscription/extra-numbers
openapi: 3.1.0
info:
  title: API Pilot Status
  version: 1.0.0
  license:
    name: Pilot Status Terms of Service
    url: https://pilotstatus.com.br/terms
  description: >-
    API REST pública do Pilot Status. Autentique com o header `x-api-key:
    ps_...` (ou `x-api-key-id`). Base URL: https://pilotstatus.com.br
servers:
  - url: https://pilotstatus.com.br
security:
  - apiKey: []
  - apiKeyId: []
paths:
  /v1/subscription/extra-numbers:
    post:
      tags:
        - Subscription
      summary: Comprar número extra (On Demand)
      description: >-
        Compra N número(s) extra(s). Requer key escopada a TENANT. Tenant Grátis
        é convertido para On Demand (cobra o número grátis convertido + o novo)
        — exige confirm:true, senão retorna 409 CONFIRMATION_REQUIRED com a
        prévia. Cobra o valor proporcional na hora — primeiro os créditos da
        carteira, o restante no cartão salvo (Planos → Cartão). Nunca devolve
        link de checkout: sem créditos suficientes e sem cartão salvo, retorna
        402 INSUFFICIENT_FUNDS e não cobra nada; cartão recusado é 402
        PAYMENT_FAILED; falha no processador é 502 PAYMENT_PROVIDER_ERROR.
        Efeito real: cobra de verdade. Só Stripe.


        **Requer uma chave com escopo de tenant.**
      operationId: post_subscription_extra-numbers
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                quantity:
                  type: number
                  description: Quantos números comprar (default 1).
                  example: '1'
                confirm:
                  type: boolean
                  description: >-
                    Obrigatório (true) quando a compra converte o plano (Grátis
                    → On Demand).
                  example: 'true'
      responses:
        '200':
          description: >-
            Comprou 1 número extra num plano que já é On Demand, com metade do
            ciclo restante. O custo proporcional saiu primeiro dos créditos da
            carteira e o resto do cartão salvo (`creditsUsed` + `cardCharged` =
            `proratedTotal`); `paidExtraNumbers` já conta a vaga nova. `plan` é
            o plano que a conta tinha antes desta chamada, e `monthlyTotalBRL`
            precifica só a `quantity` pedida: numa conversão Free → On Demand
            eles vêm `FREE` e 1 × o preço unitário, embora 2 números passem a
            ser cobrados dali em diante.
          content:
            application/json:
              example:
                success: true
                charged: true
                plan: ON_DEMAND
                paidExtraNumbers: 3
                proratedTotal: 14.95
                currency: BRL
                creditsUsed: 10
                cardCharged: 4.95
                monthlyTotalBRL: 29.9
        '400':
          description: >-
            Payload ou parâmetros inválidos — ou `INVALID_BODY` quando o corpo
            não é um JSON válido: nada é cobrado nem agendado. Corpo vazio
            continua significando `quantity = 1`
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              examples:
                validation:
                  summary: Erro de validação
                  value:
                    error: Validation error
                invalidBody:
                  summary: Corpo não é um JSON válido
                  value:
                    error: >-
                      The request body is not valid JSON | O corpo da requisição
                      não é um JSON válido
                    code: INVALID_BODY
        '401':
          description: Header `x-api-key` / `x-api-key-id` ausente ou inválido
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unauthorized
        '402':
          description: >-
            O pagamento não passou e o número extra NÃO foi adicionado. Decida
            pelo `code`, não pelo status. `INSUFFICIENT_FUNDS`: a carteira não
            cobre o `proratedTotal` e não há cartão salvo — nesta rota o
            `walletReason` é sempre `NO_CARD`; nada é cobrado e os créditos já
            descontados são devolvidos. `PAYMENT_FAILED`: a cobrança foi tentada
            e não se concluiu; o `declineCode` só vem quando o emissor do cartão
            recusou. O `error` é uma mensagem legível, em português.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - INSUFFICIENT_FUNDS
                      - PAYMENT_FAILED
                  proratedTotal:
                    type: number
                    description: O valor proporcional que foi tentado.
                  currency:
                    type: string
                    example: BRL
                  walletReason:
                    type: string
                    enum:
                      - NO_CARD
                    description: Só em INSUFFICIENT_FUNDS.
                  declineCode:
                    type: string
                    description: >-
                      Só em PAYMENT_FAILED, e só quando o emissor do cartão
                      recusou: o próprio código de recusa da bandeira, literal.
                    example: insufficient_funds
              examples:
                insufficientFunds:
                  summary: INSUFFICIENT_FUNDS
                  value:
                    error: >-
                      Saldo insuficiente na carteira e cartão indisponível para
                      cobrar o número extra. Adicione créditos ou um cartão.
                    code: INSUFFICIENT_FUNDS
                    proratedTotal: 14.95
                    currency: BRL
                    walletReason: NO_CARD
                paymentFailed:
                  summary: PAYMENT_FAILED
                  value:
                    error: Seu cartão não tem saldo suficiente.
                    code: PAYMENT_FAILED
                    proratedTotal: 14.95
                    currency: BRL
                    declineCode: insufficient_funds
        '403':
          description: Chave com escopo de número usada em endpoint exclusivo de tenant
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Number-scoped keys cannot call tenant endpoints
                code: NUMBER_SCOPE_NOT_ALLOWED
        '409':
          description: >-
            A compra converte o plano Grátis em On Demand, o que muda o valor
            pago — reenvie com `confirm: true`. A prévia completa vem junto.
          content:
            application/json:
              example:
                error: >-
                  Confirmação necessária: comprar um número extra converte o
                  plano Grátis em On Demand (todos os números passam a ser
                  cobrados). Reenvie com confirm:true.
                code: CONFIRMATION_REQUIRED
                preview:
                  plan: FREE
                  requiresConversion: true
                  explanation:
                    pt: >-
                      Sua conta passa para o plano On Demand: mensagens
                      ilimitadas e R$ 29,90/mês por número conectado. No On
                      Demand todo número é pago — o número que você já usa deixa
                      de ser gratuito. Com o novo, são 2 números a R$ 29,90: R$
                      59,80/mês.
                    en: >-
                      Your account moves to the On Demand plan: unlimited
                      messages and R$ 29.90/month per connected number. On On
                      Demand every number is paid — the number you already use
                      stops being free. With the new one, that is 2 numbers at
                      R$ 29.90: R$ 59.80/month.
                  currentNumbers: 1
                  requested: 1
                  billedQuantity: 2
                  currency: BRL
                  unitPriceMonthly: 29.9
                  proratedUnitPrice: 14.95
                  proratedTotal: 29.9
                  remainingDays: 15
                  totalDaysInCycle: 30
                  walletBalance: 0
                  hasSavedCard: false
                  fundingSource: none
                  canFund: false
                  unitPriceBRL: 29.9
                  monthlyTotalBRL: 59.8
        '429':
          description: Limite de taxa excedido
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Too many requests
        '502':
          description: >-
            O processador de pagamento não respondeu (falha de rede, um 5xx ou
            uma chave inválida do nosso lado). O número extra NÃO foi
            adicionado, mas a cobrança pode ter sido concluída no processador
            com a resposta perdida — confira seu extrato antes de tentar de
            novo. O `error` é uma mensagem legível, em português.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - PAYMENT_PROVIDER_ERROR
                  proratedTotal:
                    type: number
                    description: O valor proporcional que foi tentado.
                  currency:
                    type: string
                    example: BRL
              example:
                error: >-
                  Não conseguimos confirmar a cobrança com o processador de
                  pagamento. O número extra NÃO foi adicionado. Confira seu
                  extrato antes de tentar de novo.
                code: PAYMENT_PROVIDER_ERROR
                proratedTotal: 14.95
                currency: BRL
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Sua chave de API ps_
    apiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: Id da chave de API (alternativa ao x-api-key)

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.