> ## 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 linhas

> ⚠️ **Efeito colateral real: esta chamada cobra dinheiro — NÃO é uma simulação, inclusive pelo playground.** Cada número custa o preço mensal cheio (R$ 33,90 para workspaces em BRL, US$ 33.90 em USD), descontado **primeiro dos créditos da sua carteira** e depois do seu **cartão salvo**, e vira uma linha telefônica do seu workspace, `ACTIVE` e com o primeiro mês pago. Não há proporcional nem link de checkout: sem créditos e sem cartão salvo, o item falha com `PAYMENT_FAILED`.

Compra de **1 a 10** números obtidos em `GET /v1/phone-lines/available`. Os números são **independentes**: cada um é cobrado e adquirido separadamente, na ordem em que você os enviou, e um que falha **não** desfaz os outros. Por isso a requisição responde **`200`** quando foi processada, com um resultado por número — **um `200` não significa que você comprou alguma coisa**; sempre leia `results[i].ok`. Um item que falhou traz `code` e um `error` bilíngue, mas nenhum status HTTP próprio:

| `code` do item | O que aconteceu | Cobrado? |
| --- | --- | --- |
| `REQUEST_ALREADY_PROCESSED` | Esta `Idempotency-Key` já foi usada para este número na última hora — qualquer que tenha sido o resultado daquela tentativa. | Não por esta requisição. |
| `NUMBER_UNAVAILABLE` | O número já pertence a uma linha (de qualquer workspace, inclusive o seu), está sendo comprado em outra requisição neste momento, ou a operadora não o tem mais. | Não — ou estornado para a carteira. |
| `PAYMENT_FAILED` | Créditos mais cartão salvo não cobriram o preço, ou a cobrança no cartão não foi concluída. | Não — os créditos descontados foram devolvidos. |
| `PAYMENT_PROVIDER_ERROR` | O processador do cartão não respondeu. O cartão **pode** ter sido cobrado. | Carteira: não. Cartão: confira a fatura. |
| `PURCHASE_NOT_COMPLETED` | Cobrado e adquirido, mas não foi possível registrar a linha do nosso lado. | Estornado para a carteira. |
| `SUPPLIER_UNAVAILABLE` | A operadora falhou ao adquirir o número. | Estornado para a carteira. |

Um estorno depois de uma cobrança sempre vai para a **carteira**, inclusive a parte paga com cartão.

Qualquer resposta diferente de `200` significa que **nenhum número foi tentado**, com uma exceção: um `500` (ou um timeout) pode vir depois que números anteriores já foram cobrados e comprados — repita **a mesma requisição com a mesma `Idempotency-Key`** e depois liste suas linhas com `GET /v1/phone-lines`.

**Idempotência (header obrigatório).** A chave é rastreada **por número, durante uma hora**: a mesma chave com o mesmo número não cobra nada e responde `ok: false`, `REQUEST_ALREADY_PROCESSED` — tanto se a primeira tentativa comprou o número quanto se ela falhou (uma repetição não devolve o resultado original). Para tentar de novo um número que falhou, use uma chave **nova**. Uma requisição recusada por inteiro (`400`/`403`) não registra nada, então pode ser reenviada com a mesma chave. A chave é restrita ao seu workspace. Se o cache de idempotência estiver indisponível, a proteção deixa de ser aplicada, para não bloquear as compras.

**Verificação do titular antes.** O titular da conta precisa ser verificado (por `/v1/phone-lines/verification` ou no painel): nunca iniciada ou não concluída → `403 VERIFICATION_REQUIRED`; recusada → `403 VERIFICATION_REJECTED`; em análise manual → segue, mas as linhas compradas durante a análise são devolvidas, com as cobranças estornadas para a carteira, se ela for recusada.

Permissões: com um token OAuth / MCP, só o **Proprietário** do workspace pode comprar (`phone_lines:purchase`) — caso contrário, `403 PERMISSION_DENIED`.

Todo endpoint `/v1/phone-lines` aceita só uma chave **com escopo de tenant**: a linha pertence ao workspace, não a um número de WhatsApp, então uma chave com escopo de número (ou uma concessão OAuth por número) recebe `403 NUMBER_SCOPE_NOT_ALLOWED` antes de qualquer verificação de papel. Um `403 PERMISSION_DENIED` / `WORKSPACE_MEMBERSHIP_REQUIRED` é enviado com `Cache-Control: no-store`.

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

> [!WARNING]
>
> **Esta é uma compra real.** Cada número custa o preço mensal cheio, tirado primeiro dos créditos da carteira e depois do cartão salvo. Use um `Idempotency-Key` novo para cada compra e repita uma key só para tentar de novo a mesma compra. Veja [Comprar linhas](https://docs.pilotstatus.com.br/pt-BR/api/phone-lines/purchase.md).


## OpenAPI

````yaml openapi.pt.json POST /v1/phone-lines
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/phone-lines:
    post:
      tags:
        - Phone Lines
      summary: Comprar linhas telefônicas
      description: >-
        ⚠️ **Efeito colateral real: esta chamada cobra dinheiro — NÃO é uma
        simulação, inclusive pelo playground.** Cada número custa o preço mensal
        cheio (R$ 33,90 para workspaces em BRL, US$ 33.90 em USD), descontado
        **primeiro dos créditos da sua carteira** e depois do seu **cartão
        salvo**, e vira uma linha telefônica do seu workspace, `ACTIVE` e com o
        primeiro mês pago. Não há proporcional nem link de checkout: sem
        créditos e sem cartão salvo, o item falha com `PAYMENT_FAILED`.


        Compra de **1 a 10** números obtidos em `GET /v1/phone-lines/available`.
        Os números são **independentes**: cada um é cobrado e adquirido
        separadamente, na ordem em que você os enviou, e um que falha **não**
        desfaz os outros. Por isso a requisição responde **`200`** quando foi
        processada, com um resultado por número — **um `200` não significa que
        você comprou alguma coisa**; sempre leia `results[i].ok`. Um item que
        falhou traz `code` e um `error` bilíngue, mas nenhum status HTTP
        próprio:


        | `code` do item | O que aconteceu | Cobrado? |

        | --- | --- | --- |

        | `REQUEST_ALREADY_PROCESSED` | Esta `Idempotency-Key` já foi usada para
        este número na última hora — qualquer que tenha sido o resultado daquela
        tentativa. | Não por esta requisição. |

        | `NUMBER_UNAVAILABLE` | O número já pertence a uma linha (de qualquer
        workspace, inclusive o seu), está sendo comprado em outra requisição
        neste momento, ou a operadora não o tem mais. | Não — ou estornado para
        a carteira. |

        | `PAYMENT_FAILED` | Créditos mais cartão salvo não cobriram o preço, ou
        a cobrança no cartão não foi concluída. | Não — os créditos descontados
        foram devolvidos. |

        | `PAYMENT_PROVIDER_ERROR` | O processador do cartão não respondeu. O
        cartão **pode** ter sido cobrado. | Carteira: não. Cartão: confira a
        fatura. |

        | `PURCHASE_NOT_COMPLETED` | Cobrado e adquirido, mas não foi possível
        registrar a linha do nosso lado. | Estornado para a carteira. |

        | `SUPPLIER_UNAVAILABLE` | A operadora falhou ao adquirir o número. |
        Estornado para a carteira. |


        Um estorno depois de uma cobrança sempre vai para a **carteira**,
        inclusive a parte paga com cartão.


        Qualquer resposta diferente de `200` significa que **nenhum número foi
        tentado**, com uma exceção: um `500` (ou um timeout) pode vir depois que
        números anteriores já foram cobrados e comprados — repita **a mesma
        requisição com a mesma `Idempotency-Key`** e depois liste suas linhas
        com `GET /v1/phone-lines`.


        **Idempotência (header obrigatório).** A chave é rastreada **por número,
        durante uma hora**: a mesma chave com o mesmo número não cobra nada e
        responde `ok: false`, `REQUEST_ALREADY_PROCESSED` — tanto se a primeira
        tentativa comprou o número quanto se ela falhou (uma repetição não
        devolve o resultado original). Para tentar de novo um número que falhou,
        use uma chave **nova**. Uma requisição recusada por inteiro
        (`400`/`403`) não registra nada, então pode ser reenviada com a mesma
        chave. A chave é restrita ao seu workspace. Se o cache de idempotência
        estiver indisponível, a proteção deixa de ser aplicada, para não
        bloquear as compras.


        **Verificação do titular antes.** O titular da conta precisa ser
        verificado (por `/v1/phone-lines/verification` ou no painel): nunca
        iniciada ou não concluída → `403 VERIFICATION_REQUIRED`; recusada → `403
        VERIFICATION_REJECTED`; em análise manual → segue, mas as linhas
        compradas durante a análise são devolvidas, com as cobranças estornadas
        para a carteira, se ela for recusada.


        Permissões: com um token OAuth / MCP, só o **Proprietário** do workspace
        pode comprar (`phone_lines:purchase`) — caso contrário, `403
        PERMISSION_DENIED`.


        Todo endpoint `/v1/phone-lines` aceita só uma chave **com escopo de
        tenant**: a linha pertence ao workspace, não a um número de WhatsApp,
        então uma chave com escopo de número (ou uma concessão OAuth por número)
        recebe `403 NUMBER_SCOPE_NOT_ALLOWED` antes de qualquer verificação de
        papel. Um `403 PERMISSION_DENIED` / `WORKSPACE_MEMBERSHIP_REQUIRED` é
        enviado com `Cache-Control: no-store`.


        **Requer uma chave com escopo de tenant.**
      operationId: post_phone_lines
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            **Obrigatório.** Qualquer string de até **255 caracteres** sem
            caracteres de controle — um UUID por compra é a escolha comum.
            Rastreada por número durante uma hora, restrita ao seu workspace:
            uma nova tentativa com a mesma chave nunca cobra duas vezes pelo
            mesmo número (o item responde `REQUEST_ALREADY_PROCESSED`). Ausente,
            vazia ou só com espaços em branco → **400
            `IDEMPOTENCY_KEY_REQUIRED`**; longa demais ou com caractere de
            controle (incluindo tab) → **400 `IDEMPOTENCY_KEY_INVALID`**. É um
            header, nunca um campo do corpo.
          schema:
            type: string
            minLength: 1
            maxLength: 255
          example: 6f1c2a0e-7b1d-4c2e-9a5f-3d8e1b2c4a70
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - numbers
              additionalProperties: false
              properties:
                numbers:
                  type: array
                  minItems: 1
                  maxItems: 10
                  uniqueItems: true
                  items:
                    type: string
                    pattern: ^55[1-9][0-9][0-9]{8}$
                  description: >-
                    De 1 a 10 números distintos, cada um exatamente como `GET
                    /v1/phone-lines/available` o devolveu em `number`: dígitos
                    E.164 **sem** o `+` — `55`, um DDD de dois dígitos começando
                    por 1–9 e depois 8 dígitos (`551148637200`). Números de DDDs
                    diferentes podem ir na mesma requisição. `numbers` é o
                    **único** campo aceito: qualquer outro é recusado com `400
                    UNKNOWN_FIELDS` (o `requestId` do painel não existe aqui).
                  example:
                    - '551148637200'
                    - '551148637201'
            example:
              numbers:
                - '551148637200'
                - '551148637201'
      responses:
        '200':
          description: >-
            Processada: um resultado por número, na ordem de envio. Leia
            `results[i].ok` — todos os itens podem ter falhado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    description: Uma entrada por número, na ordem de envio.
                    items:
                      type: object
                      required:
                        - number
                        - ok
                      properties:
                        number:
                          type: string
                          description: O número, como você o enviou.
                          example: '551148637200'
                        ok:
                          type: boolean
                          description: >-
                            `true` quando a linha foi comprada por esta
                            requisição.
                          example: true
                        lineId:
                          type: string
                          description: >-
                            Só quando `ok: true`. O `id` da nova linha, para
                            `GET /v1/phone-lines/{id}` e para os endpoints de
                            código de ativação.
                          example: cmg5k2l1n0001pl8example9x
                        code:
                          type: string
                          description: >-
                            Só quando `ok: false`. Por que este número falhou:
                            `REQUEST_ALREADY_PROCESSED`, `NUMBER_UNAVAILABLE`,
                            `PAYMENT_FAILED`, `PAYMENT_PROVIDER_ERROR`,
                            `PURCHASE_NOT_COMPLETED` ou `SUPPLIER_UNAVAILABLE`.
                          enum:
                            - REQUEST_ALREADY_PROCESSED
                            - NUMBER_UNAVAILABLE
                            - PAYMENT_FAILED
                            - PAYMENT_PROVIDER_ERROR
                            - PURCHASE_NOT_COMPLETED
                            - SUPPLIER_UNAVAILABLE
                          example: PAYMENT_FAILED
                        error:
                          type: string
                          description: >-
                            Só quando `ok: false`. Mensagem bilíngue (`English |
                            Portuguese`) para esse `code`.
                          example: >-
                            The payment did not go through. Add balance or check
                            your card. | O pagamento não foi concluído. Adicione
                            saldo ou confira seu cartão.
              examples:
                mixed:
                  summary: Um comprado, uma falha de pagamento
                  value:
                    results:
                      - number: '551148637200'
                        ok: true
                        lineId: cmg5k2l1n0001pl8example9x
                      - number: '551148637201'
                        ok: false
                        code: PAYMENT_FAILED
                        error: >-
                          The payment did not go through. Add balance or check
                          your card. | O pagamento não foi concluído. Adicione
                          saldo ou confira seu cartão.
                replay:
                  summary: Repetição com a mesma chave
                  value:
                    results:
                      - number: '551148637200'
                        ok: false
                        code: REQUEST_ALREADY_PROCESSED
                        error: >-
                          This request was already processed. Check your lines,
                          or send a new request id (Idempotency-Key in the API)
                          to try again. | Este pedido já foi processado. Confira
                          suas linhas, ou envie um novo id de pedido
                          (Idempotency-Key na API) para tentar de novo.
                      - number: '551148637201'
                        ok: false
                        code: REQUEST_ALREADY_PROCESSED
                        error: >-
                          This request was already processed. Check your lines,
                          or send a new request id (Idempotency-Key in the API)
                          to try again. | Este pedido já foi processado. Confira
                          suas linhas, ou envie um novo id de pedido
                          (Idempotency-Key na API) para tentar de novo.
        '400':
          description: >-
            Requisição recusada por inteiro — nada foi tentado nem cobrado. As
            mensagens de validação são bilíngues e podem trazer `details`
            (`maxLength`, `unknownFields`, `invalidNumbers`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
                  details:
                    type: object
              examples:
                idempotencyKeyRequired:
                  summary: Sem Idempotency-Key
                  value:
                    error: >-
                      Send an Idempotency-Key header (for example a UUID) so a
                      retry cannot buy twice. | Envie o header Idempotency-Key
                      (por exemplo um UUID) para que uma nova tentativa não
                      compre duas vezes.
                    code: IDEMPOTENCY_KEY_REQUIRED
                idempotencyKeyInvalid:
                  summary: Idempotency-Key longa demais / caractere de controle
                  value:
                    error: >-
                      Idempotency-Key must have at most 255 characters and no
                      control characters. | Idempotency-Key deve ter no máximo
                      255 caracteres e nenhum caractere de controle.
                    code: IDEMPOTENCY_KEY_INVALID
                    details:
                      maxLength: 255
                invalidBody:
                  summary: O corpo não é um objeto JSON
                  value:
                    error: >-
                      Send a JSON object: { "numbers": ["551148637200"] }. |
                      Envie um objeto JSON: { "numbers": ["551148637200"] }.
                    code: INVALID_BODY
                unknownFields:
                  summary: Campo além de numbers
                  value:
                    error: >-
                      Unknown field(s): requestId. This endpoint accepts only
                      "numbers"; the idempotency key goes in the Idempotency-Key
                      header. | Campo(s) desconhecido(s): requestId. Este
                      endpoint aceita apenas "numbers"; a chave de idempotência
                      vai no header Idempotency-Key.
                    code: UNKNOWN_FIELDS
                    details:
                      unknownFields:
                        - requestId
                numbersNotArray:
                  summary: numbers não é uma lista de strings
                  value:
                    error: >-
                      "numbers" must be an array of strings. | "numbers" deve
                      ser uma lista de textos.
                    code: INVALID_NUMBERS
                invalidNumbers:
                  summary: Entradas sem o formato de um número buscado
                  value:
                    error: >-
                      Not a number from GET /v1/phone-lines/available:
                      +551148637201, 11948637202. Use E.164 digits without "+",
                      e.g. 551148637200. | Não é um número de GET
                      /v1/phone-lines/available: +551148637201, 11948637202. Use
                      os dígitos E.164 sem "+", ex.: 551148637200.
                    code: INVALID_NUMBERS
                    details:
                      invalidNumbers:
                        - '+551148637201'
                        - '11948637202'
                invalidSelection:
                  summary: Mais de 10, zero ou números repetidos
                  value:
                    error: >-
                      Pick between 1 and 10 numbers from the search. | Escolha
                      entre 1 e 10 números da busca.
                    code: INVALID_SELECTION
        '401':
          description: >-
            Credencial ausente ou inválida (`x-api-key`, `x-api-key-id` ou
            `Authorization: Bearer`). O corpo traz só `error`, sem `code`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              examples:
                missing:
                  summary: Sem credencial
                  value:
                    error: >-
                      Missing x-api-key, x-api-key-id or Authorization Bearer
                      token
                invalid:
                  summary: API key inválida
                  value:
                    error: Invalid API key
        '403':
          description: >-
            Recusada por inteiro, nada tentado: escopo da chave, papel,
            workspace arquivado, ou titular da conta não verificado / recusado.
            `TENANT_REQUIRED`: credencial sem workspace
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              examples:
                numberScope:
                  summary: Chave com escopo de número
                  value:
                    error: This endpoint requires a tenant-scoped API key
                    code: NUMBER_SCOPE_NOT_ALLOWED
                permissionDenied:
                  summary: Usuário OAuth/MCP que não é o Proprietário
                  value:
                    error: >-
                      Your workspace role does not allow this action
                      (phone_lines:purchase).
                    code: PERMISSION_DENIED
                notMember:
                  summary: Usuário OAuth/MCP que não é mais membro
                  value:
                    error: >-
                      The user behind this token is not an active member of this
                      workspace.
                    code: WORKSPACE_MEMBERSHIP_REQUIRED
                archived:
                  summary: Workspace arquivado
                  value:
                    error: >-
                      This workspace has been archived and its API is closed. |
                      Este workspace foi arquivado e sua API está encerrada.
                    code: WORKSPACE_ARCHIVED
                verificationRequired:
                  summary: Titular não verificado
                  value:
                    error: >-
                      Verify the account holder before buying a line. |
                      Verifique o titular da conta antes de comprar uma linha.
                    code: VERIFICATION_REQUIRED
                verificationRejected:
                  summary: Verificação recusada
                  value:
                    error: >-
                      The account holder verification was rejected. | A
                      verificação do titular foi recusada.
                    code: VERIFICATION_REJECTED
                tenantRequired:
                  summary: Credencial sem workspace
                  value:
                    error: This credential is not bound to a workspace.
                    code: TENANT_REQUIRED
        '404':
          description: >-
            Foi enviado um header `x-whatsapp-number-id` que não indica nenhum
            número de WhatsApp da sua conta (`NUMBER_NOT_FOUND`). Este endpoint
            não precisa desse header — remova-o.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: >-
                  x-whatsapp-number-id does not name a WhatsApp number of this
                  account | x-whatsapp-number-id não indica um número de
                  WhatsApp desta conta
                code: NUMBER_NOT_FOUND
        '500':
          description: >-
            Falha inesperada (`INTERNAL_ERROR`). **Não prova que nada
            aconteceu**: números anteriores podem já ter sido cobrados e
            comprados. Repita a mesma requisição com a **mesma**
            `Idempotency-Key` e depois liste suas linhas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: Unexpected error. | Erro inesperado.
                code: INTERNAL_ERROR
        '503':
          description: >-
            A integração com a operadora está indisponível antes de qualquer
            número ser tentado (`SUPPLIER_UNAVAILABLE`). Nada foi cobrado.
            (Falhas da operadora durante uma compra voltam por item, dentro de
            um `200`.)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
              example:
                error: >-
                  Phone lines are temporarily unavailable. Try again in a few
                  minutes. | As linhas estão temporariamente indisponíveis.
                  Tente de novo em alguns minutos.
                code: SUPPLIER_UNAVAILABLE
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.