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

# Guardar cartão

> Guarda um cartão no cofre sem cobrar nada, e devolve o pm_ das cobranças seguintes.

<Note>
  Quando existe uma primeira venda, prefira `save_card: true` nela: é uma chamada a menos, e o
  cartão entra no cofre com uma **autorização real** no histórico. Esta rota é para os casos em que
  não há primeira venda ainda — assinatura com teste grátis, cobrança adiada, upsell combinado antes
  de qualquer cobrança.
</Note>

O número do cartão não entra aqui. O que entra é o `token` de uso único que o
[`vext.js`](/api-reference/cartao/vext-js) criou no navegador — o mesmo token de uma cobrança. Ele
vale cerca de um minuto.

<Warning>
  Ao guardar, pedimos ao provedor uma **verificação sem valor**: ele consulta a bandeira e o emissor
  para saber se o cartão existe e pode transacionar, sem tocar no limite do comprador. Não é uma
  autorização — um cartão guardado por aqui passou por uma consulta, não por uma venda. A diferença
  aparece na taxa de aprovação da primeira cobrança.
</Warning>

O mesmo plástico guardado de novo devolve o **mesmo** `pm_`, e não cria um segundo. Ver
[Cofre de cartões](/essenciais/cofre-de-cartoes).


## OpenAPI

````yaml api-reference/openapi.yaml POST /v1/cards
openapi: 3.1.0
info:
  title: Vext - API de pagamentos PIX com split
  version: 1.0.0
  summary: Cobranças PIX, estornos e saldo do vendedor.
  description: |
    API pública do gateway. Todo endpoint desta versão vive sob `/v1`.

    ## Dinheiro é sempre CENTAVOS INTEIROS

    Não há exceção. `amount`, `platform_fee`, `net_amount`, `refunded_amount`,
    `fee_returned`, `seller_debit`, `balance`, `available`, `reserved`, `debt`
    e `withdrawable` são inteiros em centavos de real.

    | Valor       | Envie / receba |
    |-------------|----------------|
    | R$ 1,00     | `100`          |
    | R$ 10,50    | `1050`         |
    | R$ 1.234,56 | `123456`       |

    Enviar `10.50` devolve `422`. A recusa é deliberada: `10.50` chega ao
    servidor como ponto flutuante, `10.50 * 100` não é exatamente `1050` em
    binário, e o centavo perdido só aparece na conciliação do mês. Diante de
    um decimal preferimos recusar a adivinhar a intenção - com dinheiro,
    adivinhar sai caro.

    ## Autenticação

    Toda chamada leva a chave no header:

    ```
    Authorization: Bearer sk_live_SUA_CHAVE_SECRETA
    ```

    A chave é criada em **Desenvolvedores** no painel e o valor em claro
    aparece **uma única vez**, na criação - guardamos apenas um hash
    SHA-256. Perdeu, gere outra.

    Cada chave carrega escopos (`charges:read`, `charges:write`,
    `refunds:write`, `balance:read`, `cards:manage`). Chamar um endpoint
    fora do escopo devolve `403`, e não `401`: a chave está certa, faltou
    permissão.

    Chaves de teste usam o prefixo `sk_test_`. A diferença é visível a olho
    nu de propósito - uma chave de teste colada em produção precisa ser
    reconhecível antes de alguém passar a tarde investigando.

    ## Idempotência

    Envie `Idempotency-Key` em todo `POST`. A conexão que cai depois da
    requisição e antes da resposta deixa você sem saber se cobrou; repetir
    com a mesma chave resolve isso sem risco de cobrar duas vezes.

    - **mesma chave, mesmo corpo** → a resposta gravada, com
      `Idempotent-Replayed: true`. A cobrança não é recriada.
    - **mesma chave, corpo diferente** → `409 idempotency_key_reused`.
      Devolver a cobrança antiga faria você acreditar que cobrou o valor
      novo.
    - **mesma chave, primeira ainda em voo** → `409
      idempotency_key_in_progress`. Repita em instantes.

    A ordem dos campos no JSON não invalida o retry. A chave vale por 24
    horas e é sua: dois clientes diferentes podem usar `pedido-1` sem
    colidir.

    ## Erros

    Todo erro - inclusive os que não previmos - tem a mesma forma:

    ```json
    {
      "error": {
        "type": "invalid_request_error",
        "code": "amount_below_minimum",
        "message": "…",
        "details": {}
      }
    }
    ```

    Trate pelo `code`, nunca pela `message`: o código é contrato, o texto
    pode ser reescrito a qualquer momento.
  contact:
    name: Suporte Vext
    url: https://wa.me/5511936185272
servers:
  - url: https://api.usevext.com
    description: Produção
security:
  - apiKey: []
tags:
  - name: Cobranças
    description: Criação, consulta e estorno de cobranças, no PIX e no cartão.
  - name: Saldo
    description: Quanto o vendedor tem e quanto pode sacar.
  - name: Cartão no navegador
    description: |
      O que o navegador do comprador precisa para virar um formulário de
      cartão: a credencial de tokenização e o token do desafio de
      autenticação. O número do cartão nunca passa por esta API.
  - name: Cartões
    description: |
      Os cartões que os compradores deixaram guardados, para cobrar de novo
      sem pedir nada a eles.
paths:
  /v1/cards:
    post:
      tags:
        - Cartões
      summary: Guardar cartão sem cobrar
      description: |
        Guarda um cartão no cofre **sem cobrar nada**, e devolve o `pm_` que
        as cobranças seguintes vão usar em `card.id`.

        É a porta da assinatura com teste grátis, da cobrança adiada e do
        upsell combinado antes da primeira venda - os casos em que o cartão
        precisa estar guardado **antes** de existir cobrança. Quando há uma
        primeira venda, prefira `save_card: true` nela: é uma chamada a
        menos e o cartão entra no cofre com uma autorização real no
        histórico.

        O número do cartão **não** entra aqui. O que entra é o `token` de
        uso único que o `GET /v1/vext.js` criou no navegador - o mesmo token
        de uma cobrança. Ele vale cerca de um minuto e serve uma vez só.

        Ao guardar, pedimos ao provedor uma **verificação sem valor** (Zero
        Dollar Auth): ele consulta a bandeira e o emissor para saber se o
        cartão existe e pode transacionar, sem tocar no limite do
        comprador. Não é uma autorização - um cartão guardado por aqui
        passou por uma consulta, não por uma venda.

        Guardar credencial de pagamento sem o comprador ter pedido é
        problema regulatório antes de ser problema de produto. O
        consentimento é seu para colher.
      operationId: createSavedCard
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSavedCardRequest'
            examples:
              assinatura:
                summary: Cartão para cobrar depois
                value:
                  customer:
                    name: Ana P Souza
                    email: ana@exemplo.com.br
                    document: '11144477735'
                    phone_ddd: '11'
                    phone_number: '987654321'
                  card:
                    token: token_ORP978GFOUoQD58x
                    holder_name: ANA P SOUZA
                    billing_address:
                      zip_code: '01310100'
                      street: Avenida Paulista
                      number: '1000'
                      neighborhood: Bela Vista
                      city: São Paulo
                      state: SP
      responses:
        '201':
          description: |
            Cartão guardado. O mesmo plástico guardado de novo devolve o
            **mesmo** `pm_` - não cria um segundo.
          headers:
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedCard'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          description: |
            O corpo não passou, o cartão não passou na verificação, ou esta
            conta não vende no cartão (`card_not_available`). Nenhum cartão
            é guardado em nenhum desses casos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/ProviderUnavailable'
      security:
        - apiKey:
            - cards:manage
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Identificador único da SUA tentativa. Use o mesmo valor ao repetir
        a requisição depois de um erro de rede.

        Opcional, mas recomendado em toda criação de cobrança e estorno.
        Sem ele, uma repetição cria uma segunda cobrança.
      schema:
        type: string
        maxLength: 255
      example: pedido-1042
  schemas:
    CreateSavedCardRequest:
      type: object
      description: |
        O corpo de `POST /v1/cards`. Mesmo vocabulário de `POST /v1/charges`
        de propósito: quem já integra a cobrança no cartão não tem um
        segundo formato para aprender.

        O que não existe aqui: `amount`, `items` e `installments` - não há
        venda -, e `card.id` - esta rota **cria** a referência do cofre, e
        receber uma já existente seria pedir para guardar o que já está
        guardado. Mandar qualquer um deles devolve `422`.
      required:
        - customer
        - card
      properties:
        customer:
          $ref: '#/components/schemas/Customer'
        card:
          type: object
          required:
            - token
            - holder_name
            - billing_address
          properties:
            token:
              type: string
              maxLength: 255
              description: |
                O token de uso único criado no navegador por
                `Vext(pk).createToken(...)` - ver `GET /v1/vext.js`. Vale
                cerca de um minuto: tokenize no envio do formulário, nunca
                na abertura da página.
              example: token_ORP978GFOUoQD58x
            holder_name:
              type: string
              maxLength: 64
              description: O nome impresso no cartão, sem acento.
              example: ANA P SOUZA
            holder_document:
              type:
                - string
                - 'null'
              maxLength: 20
              description: CPF ou CNPJ do portador, quando diferente do comprador.
            billing_address:
              type:
                - object
                - 'null'
              description: |
                **Opcional**, e nos mesmos três níveis de `POST /v1/charges`:
                os seis campos, só `zip_code` + `number` (completamos o resto
                pelo CEP), ou nada.

                Aqui ele pesa mais que numa cobrança. É este endereço que o
                antifraude vai ler na PRIMEIRA cobrança deste cartão, que
                pode acontecer semanas depois - guardar sem endereço hipoteca
                a aprovação da assinatura inteira.
              required:
                - zip_code
                - number
              properties:
                zip_code:
                  type: string
                  maxLength: 16
                  example: '01310100'
                street:
                  type: string
                  maxLength: 120
                number:
                  type: string
                  maxLength: 20
                complement:
                  type:
                    - string
                    - 'null'
                  maxLength: 80
                neighborhood:
                  type: string
                  maxLength: 80
                city:
                  type: string
                  maxLength: 80
                state:
                  type: string
                  minLength: 2
                  maxLength: 2
                  example: SP
    SavedCard:
      type: object
      description: |
        Um cartão guardado. **Todo campo está sempre presente.**

        O que NÃO está aqui, e não vai estar: as referências da Pagar.me
        que de fato cobram, e os seis primeiros dígitos. As primeiras são o
        equivalente ao cartão para quem as tem; o BIN somado aos quatro
        últimos é material de correlação que você não precisa.
      required:
        - object
        - id
        - brand
        - last_four
        - exp_month
        - exp_year
        - holder_name
        - expired
        - created_at
        - last_used_at
      properties:
        object:
          type: string
          const: card
        id:
          type: string
          pattern: ^pm_[0-9a-z]{26}$
        brand:
          type:
            - string
            - 'null'
          enum:
            - visa
            - mastercard
            - elo
            - hipercard
            - amex
            - diners
            - discover
            - aura
            - null
        last_four:
          type:
            - string
            - 'null'
        exp_month:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 12
        exp_year:
          type:
            - integer
            - 'null'
        holder_name:
          type:
            - string
            - 'null'
        expired:
          type: boolean
          description: |
            Vencido é ESTADO, e não ausência: o cartão continua na lista
            para você entender por que a cobrança de um clique parou de
            funcionar para aquele comprador.
        created_at:
          type:
            - string
            - 'null'
          format: date-time
        last_used_at:
          type:
            - string
            - 'null'
          format: date-time
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
            - details
          properties:
            type:
              type: string
              description: |
                Família do erro. Responde "de quem é o problema e o que
                fazer": `invalid_request_error` se corrige mudando a
                chamada, `authentication_error` trocando a chave, e
                `api_error` é nosso - repita, não reescreva.
              enum:
                - authentication_error
                - permission_error
                - invalid_request_error
                - idempotency_error
                - not_found_error
                - rate_limit_error
                - api_error
                - card_error
            code:
              type: string
              description: |
                Código estável do erro. **Trate por ele.** A `message` pode
                ser reescrita a qualquer momento; o código, não.
            message:
              type: string
              description: Explicação em português, para leitura humana.
            details:
              type: object
              description: |
                Sempre objeto, mesmo vazio. Em `validation_failed`, traz os
                campos recusados e suas mensagens.
              additionalProperties: true
    Customer:
      type: object
      required:
        - name
        - email
        - document
        - phone_ddd
        - phone_number
      properties:
        name:
          type: string
          maxLength: 120
          example: João Comprador
        email:
          type: string
          format: email
          maxLength: 180
          description: |
            Obrigatório: sem e-mail o provedor aceita o pedido e devolve
            uma cobrança sem QR code.
          example: joao@exemplo.com.br
        document:
          type: string
          maxLength: 20
          description: |
            CPF ou CNPJ. Pontuação é aceita e descartada - guardamos apenas
            os dígitos. Obrigatório: sem documento o provedor aceita o
            pedido e devolve uma cobrança sem QR code.
          example: 111.444.777-35
        document_type:
          type:
            - string
            - 'null'
          enum:
            - CPF
            - CNPJ
            - PASSPORT
            - null
        phone_ddd:
          type: string
          maxLength: 3
          description: |
            DDD de quem vai pagar. Obrigatório: sem telefone o provedor
            aceita o pedido e devolve uma cobrança sem QR code.
          example: '11'
        phone_number:
          type: string
          maxLength: 12
          description: |
            Número sem o DDD. Oito dígitos (fixo) ou nove (celular, sempre
            começando com 9).
          example: '987654321'
  headers:
    IdempotentReplayed:
      description: |
        Presente com valor `true` quando o corpo devolvido é uma resposta
        **gravada**, e não um recurso novo. Nada foi criado nesta chamada.
      schema:
        type: string
        enum:
          - 'true'
  responses:
    Unauthorized:
      description: |
        Chave ausente, desconhecida, revogada ou expirada. A revogada é
        distinguida da inexistente na MENSAGEM, nunca no status: quem
        integra precisa saber que a chave existiu e foi desligada, e quem
        está adivinhando tokens não aprende nada com isso.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            ausente:
              summary: missing_api_key
              value:
                error:
                  type: authentication_error
                  code: missing_api_key
                  message: 'Envie a chave em Authorization: Bearer sk_live_...'
                  details: {}
            invalida:
              summary: invalid_api_key
              value:
                error:
                  type: authentication_error
                  code: invalid_api_key
                  message: Chave de API inválida.
                  details: {}
            revogada:
              summary: revoked_api_key
              value:
                error:
                  type: authentication_error
                  code: revoked_api_key
                  message: >-
                    Esta chave de API foi revogada. Gere uma nova em
                    Desenvolvedores.
                  details: {}
            expirada:
              summary: expired_api_key
              value:
                error:
                  type: authentication_error
                  code: expired_api_key
                  message: Esta chave de API expirou. Gere uma nova em Desenvolvedores.
                  details: {}
    Forbidden:
      description: |
        A chave é válida, mas não tem o escopo do endpoint. `403` e não
        `401` de propósito: `401` diria "sua chave está errada" a quem tem
        a chave certa.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            escopo:
              summary: insufficient_scope
              value:
                error:
                  type: permission_error
                  code: insufficient_scope
                  message: Esta chave não tem o escopo [refunds:write].
                  details:
                    required_ability: refunds:write
                    abilities:
                      - charges:read
                      - charges:write
            umClique:
              summary: one_click_requires_three_ds
              value:
                error:
                  type: permission_error
                  code: one_click_requires_three_ds
                  message: >-
                    Esta loja exige autenticação do portador, e o pagamento de
                    um clique não a suporta.
                  details: {}
    IdempotencyConflict:
      description: Conflito de chave de idempotência.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            reusada:
              summary: idempotency_key_reused
              description: |
                A chave já foi usada com outro corpo. Devolver a resposta
                antiga faria você acreditar que cobrou o valor novo.
              value:
                error:
                  type: idempotency_error
                  code: idempotency_key_reused
                  message: >-
                    Esta chave de idempotência já foi usada com um corpo
                    diferente. Use uma chave nova.
                  details: {}
            emAndamento:
              summary: idempotency_key_in_progress
              value:
                error:
                  type: idempotency_error
                  code: idempotency_key_in_progress
                  message: >-
                    Uma requisição com esta chave ainda está em processamento.
                    Tente novamente em instantes.
                  details: {}
    RateLimited:
      description: |
        Requisições demais.

        Há três limites, por minuto: um por IP antes da autenticação, e
        depois um de leitura e um de escrita, ambos por CHAVE - o excesso
        de uma integração não consome a cota das outras do mesmo
        vendedor. A escrita é bem mais apertada porque cada cobrança
        criada vira uma chamada ao provedor.

        `Retry-After` diz em quantos segundos voltar. Os cabeçalhos
        `X-RateLimit-*` vêm em TODA resposta, não só nesta: são eles que
        permitem desacelerar antes de bater no limite.
      headers:
        Retry-After:
          description: Segundos até a cota ser reposta.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Requisições permitidas por minuto neste balde.
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Quantas ainda cabem no minuto corrente.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            cota:
              summary: too_many_requests
              value:
                error:
                  type: rate_limit_error
                  code: too_many_requests
                  message: Muitas requisições. Tente novamente em 42 segundos.
                  details:
                    retry_after: 42
            provedor:
              summary: provider_rate_limited
              value:
                error:
                  type: rate_limit_error
                  code: provider_rate_limited
                  message: >-
                    Limite de requisições do provedor atingido. Tente novamente
                    em instantes.
                  details: {}
    InternalError:
      description: |
        Erro inesperado do nosso lado. Repita com a mesma
        `Idempotency-Key` - ela é o que torna repetir seguro.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            interno:
              value:
                error:
                  type: api_error
                  code: internal_error
                  message: >-
                    Erro interno. Se persistir, informe o horário da chamada ao
                    suporte.
                  details: {}
    ProviderUnavailable:
      description: |
        O provedor de pagamento não respondeu. **Repita com a mesma
        `Idempotency-Key`**: pode ser que a cobrança tenha sido criada lá e
        a resposta se perdido no caminho, e a mesma chave devolve a
        cobrança em vez de criar outra.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            indisponivel:
              value:
                error:
                  type: api_error
                  code: provider_unavailable
                  message: >-
                    O provedor de pagamento não respondeu. Repita a requisição
                    com a mesma Idempotency-Key.
                  details: {}
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: sk_live_… | sk_test_…
      description: |
        Chave secreta do vendedor, criada no painel em **Desenvolvedores**.

        O valor em claro existe uma única vez, na criação. Guardamos só o
        hash SHA-256 - nem o suporte consegue recuperá-lo, o que é o ponto:
        um dump do nosso banco não permite cobrar em nome de ninguém.

        A chave vai no **servidor**. Colocá-la no navegador do comprador a
        entrega a qualquer pessoa que abra a página.

````