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

# Cria um pagamento por biometria (Pix confirmado no aparelho de quem paga) e devolve o link de quem paga

> Cria o pedido de um Pix que a pessoa confirma no próprio aparelho, com biometria ou bloqueio de tela. Entregue o payer_url a quem paga. O Pix é iniciado via Open Finance Brasil por uma instituição iniciadora de pagamento autorizada pelo Banco Central; o dinheiro vai direto da conta de quem paga para a conta de destino, e a UNIFOKAL não guarda nem movimenta o valor. Exige uma chave secreta criada COM o escopo pix_payments:write (chaves sem restrição não o têm) e o cadastro da organização aprovado. Idempotência pelo reference_id do corpo, sem janela e sem Idempotency-Key: repetir com o mesmo corpo devolve 200 com Idempotent-Replay: true, o estado ATUAL e o mesmo payer_url; com outro corpo é 422 idempotency_key_reuse. Não existe GET nem cancelamento pela API: o desfecho chega pelos webhooks pix_payment.succeeded, pix_payment.failed, pix_payment.expired e pix_payment.refunded, no webhook do flow. A volta do navegador de quem paga nunca prova pagamento. No sandbox (chave unifokal_test_) a confirmação acontece num banco de teste, com redirecionamento, e nenhum dado pessoal seu sai; estados e webhooks são os de produção, e nada é cobrado. Em produção, a tarifa sai do seu saldo só no Pix concluído.



## OpenAPI

````yaml /api-reference/openapi.json post /pix-payments
openapi: 3.1.0
info:
  title: UNIFOKAL API
  version: v1
  summary: >-
    Verificação de identidade: sessão criada no servidor, jornada no widget,
    resultado no webhook.
  description: >-
    A versão vive no caminho (/v1); não existe header de versão. Adição
    compatível (campo, módulo ou chave nova) NÃO sobe a versão: ignore o que não
    conhecer. Todo erro tem a forma { error, message }: o código é estável, a
    mensagem não. Ids são prefixo + ULID. Webhooks são assinados (HMAC SHA-256
    no header X-IDSAAS-Signature, janela de 300s; o segredo é SEU, mínimo 32
    caracteres) e o header x-idsaas-event carrega o tipo do evento.
  termsOfService: https://unifokal.com/termos
  contact:
    name: UNIFOKAL
    url: https://docs.unifokal.com/
servers:
  - url: https://api.unifokal.com/v1
security: []
externalDocs:
  description: >-
    Documentação completa em https://docs.unifokal.com/. Briefing para agentes
    de IA em https://unifokal.com/llms.txt.
  url: https://docs.unifokal.com/
paths:
  /pix-payments:
    post:
      summary: >-
        Cria um pagamento por biometria (Pix confirmado no aparelho de quem
        paga) e devolve o link de quem paga
      description: >-
        Cria o pedido de um Pix que a pessoa confirma no próprio aparelho, com
        biometria ou bloqueio de tela. Entregue o payer_url a quem paga. O Pix é
        iniciado via Open Finance Brasil por uma instituição iniciadora de
        pagamento autorizada pelo Banco Central; o dinheiro vai direto da conta
        de quem paga para a conta de destino, e a UNIFOKAL não guarda nem
        movimenta o valor. Exige uma chave secreta criada COM o escopo
        pix_payments:write (chaves sem restrição não o têm) e o cadastro da
        organização aprovado. Idempotência pelo reference_id do corpo, sem
        janela e sem Idempotency-Key: repetir com o mesmo corpo devolve 200 com
        Idempotent-Replay: true, o estado ATUAL e o mesmo payer_url; com outro
        corpo é 422 idempotency_key_reuse. Não existe GET nem cancelamento pela
        API: o desfecho chega pelos webhooks pix_payment.succeeded,
        pix_payment.failed, pix_payment.expired e pix_payment.refunded, no
        webhook do flow. A volta do navegador de quem paga nunca prova
        pagamento. No sandbox (chave unifokal_test_) a confirmação acontece num
        banco de teste, com redirecionamento, e nenhum dado pessoal seu sai;
        estados e webhooks são os de produção, e nada é cobrado. Em produção, a
        tarifa sai do seu saldo só no Pix concluído.
      operationId: createPixPayment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePixPaymentRequest'
      responses:
        '200':
          description: >-
            Repetição do mesmo reference_id com o mesmo corpo: o estado atual e
            o mesmo payer_url.
          headers:
            Idempotent-Replay:
              schema:
                type: string
                const: 'true'
              description: Sempre true na repetição.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixPayment'
        '201':
          description: >-
            Pagamento criado em awaiting_payer. Entregue o payer_url a quem
            paga; o link vale até expires_at.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixPayment'
        '400':
          description: >-
            Códigos estáveis: invalid_json, validation_error, webhook_required,
            webhook_endpoint_not_found, webhook_environment_mismatch,
            webhook_secret_missing. Ramifique pelo campo error, nunca pela
            mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - invalid_json
                          - validation_error
                          - webhook_required
                          - webhook_endpoint_not_found
                          - webhook_environment_mismatch
                          - webhook_secret_missing
        '401':
          description: >-
            Códigos estáveis: invalid_api_key. Ramifique pelo campo error, nunca
            pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - invalid_api_key
        '402':
          description: >-
            Códigos estáveis: insufficient_credit, insufficient_balance.
            Ramifique pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - insufficient_credit
                          - insufficient_balance
        '403':
          description: >-
            Códigos estáveis: credential_type_not_allowed,
            organization_suspended, test_key_used_in_production,
            insufficient_scope, ip_not_allowed, onboarding_required. Ramifique
            pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - credential_type_not_allowed
                          - organization_suspended
                          - test_key_used_in_production
                          - insufficient_scope
                          - ip_not_allowed
                          - onboarding_required
        '404':
          description: >-
            Códigos estáveis: flow_not_found. Ramifique pelo campo error, nunca
            pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - flow_not_found
        '413':
          description: >-
            Códigos estáveis: payload_too_large. Ramifique pelo campo error,
            nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - payload_too_large
        '422':
          description: >-
            Códigos estáveis: flow_without_pix_payment,
            module_provider_unconfigured, destination_required,
            destination_not_registered, destination_not_ready,
            institution_unknown, idempotency_key_reuse, attempt_limit_reached.
            Ramifique pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - flow_without_pix_payment
                          - module_provider_unconfigured
                          - destination_required
                          - destination_not_registered
                          - destination_not_ready
                          - institution_unknown
                          - idempotency_key_reuse
                          - attempt_limit_reached
        '429':
          description: >-
            Códigos estáveis: rate_limited. Ramifique pelo campo error, nunca
            pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - rate_limited
        '503':
          description: >-
            Códigos estáveis: pix_unavailable. Ramifique pelo campo error, nunca
            pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - pix_unavailable
      security:
        - secretKey: []
components:
  schemas:
    CreatePixPaymentRequest:
      type: object
      properties:
        flow_id:
          type: string
          pattern: ^flow_[0-9A-Za-z_]{1,64}$
          description: >-
            Flow do mesmo ambiente da chave, com o módulo pix_payment e um
            webhook do mesmo ambiente: os eventos pix_payment.* chegam no
            webhook desse flow.
        reference_id:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            O id do PEDIDO no seu sistema e a chave de idempotência, permanente:
            repetir com o mesmo corpo devolve o estado atual e o mesmo payer_url
            (200, Idempotent-Replay: true); com outro corpo é 422
            idempotency_key_reuse. Depois de failed ou expired, um pagamento
            novo usa outro reference_id. Nunca ponha CPF, e-mail ou telefone
            aqui.
        amount_cents:
          type: integer
          minimum: 1
          maximum: 2147483647
          description: >-
            Valor em centavos de real, inteiro de 1 a 2147483647. Não é teto de
            negócio: o limite é o do banco de quem paga.
        description:
          type: string
          minLength: 1
          maxLength: 140
          pattern: ^[^\u0000-\u001f\u007f-\u009f]*$
          description: >-
            Opcional, até 140 caracteres, sem caractere de controle. É mostrada
            a quem paga.
        destination_id:
          type: string
          pattern: ^pxr_[0-9A-Za-z_]{1,64}$
          description: >-
            Conta de destino cadastrada no painel (pxr_). Sem destination_id nem
            destination vale o destino padrão do flow; sem nenhum dos três é 422
            destination_required.
        destination:
          type: object
          properties:
            holder_name:
              type: string
              minLength: 1
              maxLength: 140
              pattern: ^[^\u0000-\u001f\u007f-\u009f]*$
              description: Espaços nas pontas são removidos antes da validação.
            holder_document:
              type: string
              pattern: ^\d{11}$|^\d{14}$
            ispb:
              type: string
              pattern: ^\d{8}$
            branch:
              type: string
              pattern: ^\d{1,4}$
            account:
              type: string
              pattern: ^\d{1,20}$
            account_type:
              type: string
              enum:
                - checking
                - savings
          required:
            - holder_name
            - holder_document
            - ispb
            - branch
            - account
            - account_type
          description: >-
            A conta de destino por extenso, no lugar do destination_id. Em
            produção tem de casar com uma conta cadastrada no painel, ativa e já
            utilizável; no sandbox qualquer conta válida passa e nada sai.
        payer:
          type: object
          properties:
            document:
              type: string
              pattern: ^\d{11}$
            name:
              type: string
              minLength: 1
              maxLength: 140
              pattern: ^[^\u0000-\u001f\u007f-\u009f]*$
              description: Espaços nas pontas são removidos antes da validação.
          required:
            - document
            - name
          description: >-
            Opcional: CPF (11 dígitos, dígito verificador conferido) e nome de
            quem paga. Presente, a confirmação não pede o CPF de novo. Nunca
            volta em resposta nem em webhook.
        return_url:
          type: string
          description: >-
            Opcional: https para onde quem paga volta ao terminar, com
            ?pix_payment_id=pxp_... e sem estado na URL. A volta do navegador
            não prova pagamento: o webhook é a fonte da verdade.
        expires_in_seconds:
          type: integer
          minimum: 1800
          maximum: 604800
          description: >-
            Validade do link de quem paga, de 1800 (30 minutos) a 604800 (7
            dias). Padrão 86400 (24 horas).
      required:
        - flow_id
        - reference_id
        - amount_cents
      not:
        required:
          - destination_id
          - destination
    PixPayment:
      type: object
      description: >-
        Um pagamento por biometria. Nada de quem paga sai neste objeto. Campo
        novo pode surgir sem subir a versão: ignore chave desconhecida.
      required:
        - id
        - object
        - livemode
        - reference_id
        - flow_id
        - verification_session_id
        - status
        - amount_cents
        - currency
        - description
        - destination
        - payer_url
        - return_url
        - expires_at
        - created_at
        - paid_at
        - end_to_end_id
        - failure
        - last_attempt
      properties:
        id:
          $ref: '#/components/schemas/PixPaymentId'
        object:
          type: string
          const: pix_payment
        livemode:
          type: boolean
          description: true em produção (chave unifokal_live_), false no sandbox.
        reference_id:
          type:
            - string
            - 'null'
          description: O id do pedido no seu sistema, o mesmo do corpo da criação.
        flow_id:
          $ref: '#/components/schemas/FlowId'
        verification_session_id:
          type:
            - string
            - 'null'
          pattern: ^vs_[0-9A-HJKMNP-TV-Z]{26}$
          description: >-
            A sessão de verificação que criou o pagamento, quando ele nasceu no
            widget; null quando nasceu pela API.
        status:
          type: string
          enum:
            - awaiting_payer
            - processing
            - succeeded
            - failed
            - expired
            - refunded
          description: >-
            awaiting_payer: esperando quem paga; processing: a confirmação
            começou; succeeded: Pix concluído e confirmado; failed: falha
            definitiva do pedido; expired: prazo vencido sem Pix; refunded:
            devolvido depois do sucesso.
        amount_cents:
          type: integer
          minimum: 1
        currency:
          type: string
          const: BRL
        description:
          type:
            - string
            - 'null'
        destination:
          type: object
          description: A conta de destino, mascarada.
          required:
            - id
            - holder_name
            - holder_document
            - ispb
            - branch
            - account
            - account_type
          properties:
            id:
              $ref: '#/components/schemas/PixDestinationId'
            holder_name:
              type: string
            holder_document:
              type: string
              description: CPF ou CNPJ do titular, mascarado.
            ispb:
              type: string
              pattern: ^\d{8}$
            branch:
              type: string
            account:
              type: string
              description: Número da conta, mascarado.
            account_type:
              type: string
              enum:
                - checking
                - savings
        payer_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            O link de quem paga, com a credencial no fragmento (#). Só na
            resposta da criação e na repetição; null no webhook. Não registre em
            log.
        return_url:
          type:
            - string
            - 'null'
          format: uri
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        paid_at:
          type:
            - string
            - 'null'
          format: date-time
        end_to_end_id:
          type:
            - string
            - 'null'
          description: O identificador fim a fim do Pix, depois do sucesso.
        failure:
          type:
            - object
            - 'null'
          description: Só em failed e expired.
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - destination_rejected
                - pix_unavailable
                - canceled
                - bank_rejected
                - expired
            message:
              type: string
              description: Frase fixa; ramifique pelo code.
        last_attempt:
          type:
            - object
            - 'null'
          description: >-
            A categoria da última tentativa que não concluiu.
            not_completed_by_payer cobre tudo que é de quem paga (recusa, tempo,
            saldo, limite); o motivo exato só quem paga vê.
          required:
            - code
            - at
          properties:
            code:
              type: string
              enum:
                - not_completed_by_payer
                - bank_unavailable
                - same_account
                - unknown
            at:
              type:
                - string
                - 'null'
              format: date-time
    ErrorEnvelope:
      type: object
      description: >-
        Todo erro da API tem exatamente esta forma. O campo error é um código
        estável (contrato); message é texto livre e pode mudar sem aviso.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Código estável do erro. Ramifique por ele.
        message:
          type: string
          description: Texto explicativo. NÃO é contrato.
      additionalProperties: false
    PixPaymentId:
      type: string
      pattern: ^pxp_[0-9A-HJKMNP-TV-Z]{26}$
      description: Id do pagamento por biometria (pxp_ + ULID).
    FlowId:
      type: string
      pattern: ^flow_[0-9A-HJKMNP-TV-Z]{26}$
      description: >-
        Id de flow (flow_ + ULID). Criado no painel, nunca pela API da chave
        secreta.
    PixDestinationId:
      type: string
      pattern: ^pxr_[0-9A-HJKMNP-TV-Z]{26}$
      description: Id da conta de destino cadastrada no painel (pxr_ + ULID).
  securitySchemes:
    secretKey:
      type: http
      scheme: bearer
      description: >-
        Chave secreta (unifokal_live_ em produção, unifokal_test_ no sandbox: 64
        hex mais CRC-32) no Authorization: Bearer. Só no seu servidor: nunca no
        browser ou app (o widget monta apenas com o id vs_ da sessão). Não
        existe chave publishable.

````

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