> ## 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 uma sessão de verificação (a única chamada obrigatória do integrador)

> O 201 nunca traz decisão: o resultado chega no seu webhook (a fonte da verdade). IDEMPOTÊNCIA: esta rota não usa header nenhum. O reference_id (obrigatório) É a chave: a mesma organização, no mesmo ambiente, com o mesmo reference_id, recebe de volta a MESMA sessão ENQUANTO ELA VIVER (expires_in), em vez de uma segunda (o replay volta com Idempotent-Replay: true). Expirada a sessão, o mesmo reference_id abre uma sessão nova, que é o que a pessoa precisa para tentar de novo. Repetir o reference_id com um corpo DIFERENTE é 422 idempotency_key_reuse; com a primeira chamada ainda em voo, 409 idempotency_conflict. Exceto quando o corpo traz act: aí a chave é act.external_id, pelo prazo da sessão de prova.



## OpenAPI

````yaml /api-reference/openapi.json post /verification-sessions
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:
  /verification-sessions:
    post:
      summary: >-
        Cria uma sessão de verificação (a única chamada obrigatória do
        integrador)
      description: >-
        O 201 nunca traz decisão: o resultado chega no seu webhook (a fonte da
        verdade). IDEMPOTÊNCIA: esta rota não usa header nenhum. O reference_id
        (obrigatório) É a chave: a mesma organização, no mesmo ambiente, com o
        mesmo reference_id, recebe de volta a MESMA sessão ENQUANTO ELA VIVER
        (expires_in), em vez de uma segunda (o replay volta com
        Idempotent-Replay: true). Expirada a sessão, o mesmo reference_id abre
        uma sessão nova, que é o que a pessoa precisa para tentar de novo.
        Repetir o reference_id com um corpo DIFERENTE é 422
        idempotency_key_reuse; com a primeira chamada ainda em voo, 409
        idempotency_conflict. Exceto quando o corpo traz act: aí a chave é
        act.external_id, pelo prazo da sessão de prova.
      operationId: createVerificationSession
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionRequest'
      responses:
        '201':
          description: >-
            Sessão criada (monte o widget com o id vs_; nenhum segredo vai ao
            browser). OU, quando o corpo traz transaction/transactions, a
            resposta de INGESTÃO (id ing_ + contadores; nenhum vs_).
          headers:
            Idempotent-Replay:
              schema:
                type: string
              description: >-
                Presente (true) quando a resposta é o replay selado de uma
                tentativa anterior.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VerificationSession'
                  - $ref: '#/components/schemas/IngestResponse'
        '400':
          description: >-
            Códigos estáveis: invalid_json, validation_error,
            unknown_policy_key, unknown_monitoring_key,
            unknown_consultation_authorization_key, unknown_foreign_entity_key,
            unknown_beneficial_owner_key, unknown_act_key,
            unknown_esignature_key, return_url_not_allowed. Ramifique pelo campo
            error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - invalid_json
                          - validation_error
                          - unknown_policy_key
                          - unknown_monitoring_key
                          - unknown_consultation_authorization_key
                          - unknown_foreign_entity_key
                          - unknown_beneficial_owner_key
                          - unknown_act_key
                          - unknown_esignature_key
                          - return_url_not_allowed
        '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. Ramifique pelo campo error,
            nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - insufficient_credit
        '403':
          description: >-
            Códigos estáveis: email_not_verified, credential_type_not_allowed,
            organization_suspended, account_under_review,
            test_key_used_in_production. Ramifique pelo campo error, nunca pela
            mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - email_not_verified
                          - credential_type_not_allowed
                          - organization_suspended
                          - account_under_review
                          - test_key_used_in_production
        '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
        '409':
          description: >-
            Códigos estáveis: idempotency_conflict, already_settled. Ramifique
            pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - idempotency_conflict
                          - already_settled
        '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_not_live, module_dependency_violation,
            email_not_accepted, phone_not_accepted, idempotency_key_reuse,
            policy_module_not_in_flow, policy_ubo_cap_above_flow,
            pep_override_not_enabled, reference_blocklisted,
            user_auth_not_enabled, auth_required, transaction_conflict,
            transaction_required, transaction_not_supported,
            session_not_supported, batch_too_large,
            batch_not_supported_for_gate, external_id_required,
            amount_too_large, amount_above_flow_ceiling, currency_not_supported,
            event_too_old, reference_id_charset, pii_shaped_value,
            origin_tag_not_supported, metadata_not_supported,
            metadata_too_large, account_event_required,
            account_event_not_supported, account_event_ip_not_public,
            esignature_document_required, esignature_not_supported,
            consultation_authorization_required,
            consultation_authorization_not_supported, foreign_entity_required,
            foreign_entity_not_supported, foreign_entity_lei_invalid,
            foreign_entity_too_many_owners, credit_relationship_required,
            credit_relationship_not_supported, credit_purpose_required,
            credit_requester_unverified, age_contest_channel_required,
            act_requires_passkey, passkey_not_enrolled,
            passkey_bind_needs_authentication, passkey_bind_needs_reproof,
            act_hostile_char, act_kind_invalid, act_summary_invalid,
            act_counterparty_invalid, act_amount_invalid, act_currency_invalid,
            content_removed_by_notice, reference_id_required,
            unknown_settles_reference, pending_lifecycle_not_enabled,
            settlement_status_invalid, enrollment_not_found, enrollment_locked,
            document_blocklisted, session_monitoring_not_supported,
            window_too_long, pix_device_required, pix_device_module_not_in_flow,
            pix_device_reference_required, expected_address_not_supported,
            counterparty_country_invalid, pld_profile_invalid,
            pld_profile_not_supported. Ramifique pelo campo error, nunca pela
            mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - flow_not_live
                          - module_dependency_violation
                          - email_not_accepted
                          - phone_not_accepted
                          - idempotency_key_reuse
                          - policy_module_not_in_flow
                          - policy_ubo_cap_above_flow
                          - pep_override_not_enabled
                          - reference_blocklisted
                          - user_auth_not_enabled
                          - auth_required
                          - transaction_conflict
                          - transaction_required
                          - transaction_not_supported
                          - session_not_supported
                          - batch_too_large
                          - batch_not_supported_for_gate
                          - external_id_required
                          - amount_too_large
                          - amount_above_flow_ceiling
                          - currency_not_supported
                          - event_too_old
                          - reference_id_charset
                          - pii_shaped_value
                          - origin_tag_not_supported
                          - metadata_not_supported
                          - metadata_too_large
                          - account_event_required
                          - account_event_not_supported
                          - account_event_ip_not_public
                          - esignature_document_required
                          - esignature_not_supported
                          - consultation_authorization_required
                          - consultation_authorization_not_supported
                          - foreign_entity_required
                          - foreign_entity_not_supported
                          - foreign_entity_lei_invalid
                          - foreign_entity_too_many_owners
                          - credit_relationship_required
                          - credit_relationship_not_supported
                          - credit_purpose_required
                          - credit_requester_unverified
                          - age_contest_channel_required
                          - act_requires_passkey
                          - passkey_not_enrolled
                          - passkey_bind_needs_authentication
                          - passkey_bind_needs_reproof
                          - act_hostile_char
                          - act_kind_invalid
                          - act_summary_invalid
                          - act_counterparty_invalid
                          - act_amount_invalid
                          - act_currency_invalid
                          - content_removed_by_notice
                          - reference_id_required
                          - unknown_settles_reference
                          - pending_lifecycle_not_enabled
                          - settlement_status_invalid
                          - enrollment_not_found
                          - enrollment_locked
                          - document_blocklisted
                          - session_monitoring_not_supported
                          - window_too_long
                          - pix_device_required
                          - pix_device_module_not_in_flow
                          - pix_device_reference_required
                          - expected_address_not_supported
                          - counterparty_country_invalid
                          - pld_profile_invalid
                          - pld_profile_not_supported
        '429':
          description: >-
            Códigos estáveis: reauth_rate_limited, sandbox_limit_reached,
            rate_limited, daily_ingest_cap_reached, spend_cap_reached,
            volume_cap_reached, attempt_limit_reached, module_quota_reached.
            Ramifique pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - reauth_rate_limited
                          - sandbox_limit_reached
                          - rate_limited
                          - daily_ingest_cap_reached
                          - spend_cap_reached
                          - volume_cap_reached
                          - attempt_limit_reached
                          - module_quota_reached
        '503':
          description: >-
            Códigos estáveis: age_document_path_unavailable. Ramifique pelo
            campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - age_document_path_unavailable
      security:
        - secretKey: []
components:
  schemas:
    CreateSessionRequest:
      type: object
      properties:
        flow_id:
          type: string
          minLength: 1
          description: Id do flow que define os módulos da verificação (prefixo flow_).
        reference_id:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            OBRIGATÓRIO, e ele É A CHAVE DE IDEMPOTÊNCIA desta rota (não existe
            header), exceto quando o corpo traz act, cuja chave é
            act.external_id: a mesma organização, no mesmo ambiente, com o mesmo
            reference_id, recebe de volta a MESMA sessão enquanto ela viver, em
            vez de uma segunda. Volta EM CLARO no 201 e no webhook: nunca
            coloque dado pessoal aqui. De 1 a 255 caracteres: letras, dígitos e
            . _ : @ -, sem começar por - nem @. Espaço nas pontas é aparado;
            caractere invisível ou fora do conjunto é 422 reference_id_charset,
            e um valor com forma de dado pessoal (CPF, CNPJ, telefone, e-mail) é
            422 pii_shaped_value. Use o id do usuário no seu sistema, por
            exemplo um UUID.
        origin_tag:
          type: string
          maxLength: 64
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$
          description: >-
            OPCIONAL. Um rótulo SEU para a origem da jornada (a campanha, o
            canal, a tela de onde a pessoa veio), de 1 a 64 caracteres: letras,
            dígitos, ponto, sublinhado, dois-pontos e hífen, começando por letra
            ou dígito (sem espaço, arroba, barra nem interrogação, então não
            cabe e-mail nem URL). Volta EXATO, sem mudar a caixa, no 201 da
            criação e no webhook da verificação (data.origin_tag); a sessão
            renovada pelo widget herda o mesmo valor. Use minúsculas como
            convenção: ferramentas de análise tratam caixa como valor diferente.
            Nunca coloque dado pessoal aqui. Não é aceito junto do bloco de
            transação (422 origin_tag_not_supported). Como entra no corpo, a
            idempotência vale para ele também: repetir o reference_id com outro
            origin_tag é 422 idempotency_key_reuse.
        metadata:
          type: object
          maxProperties: 20
          propertyNames:
            pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,39}$
            maxLength: 40
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 500
          description: >-
            OPCIONAL. Até 20 pares de texto SEUS (o número do pedido, a filial,
            o canal) que voltam exatos no webhook da verificação (data.metadata)
            e no dossiê do painel. Chave de 1 a 40 caracteres (letra ou dígito
            na frente, depois letra, dígito, ponto, sublinhado, dois-pontos e
            hífen); valor de 1 a 500 caracteres, só texto, sem caractere de
            controle e sem =, +, - ou @ na frente. Nunca decide nada. O conjunto
            serializado em JSON UTF-8 vai até 15872 bytes, senão 422
            metadata_too_large. Nunca coloque dado pessoal aqui: chave ou valor
            com forma de CPF, CNPJ, e-mail, telefone ou chave Pix, inteiro ou no
            meio do texto, é recusado com 422 pii_shaped_value. Como entra no
            corpo, a idempotência vale para ele também.
        email:
          description: >-
            RECUSADO com 422 email_not_accepted: o e-mail é sempre digitado pelo
            titular no widget, que também dispara o código em seguida. Nunca
            envie o endereço na criação.
        phone:
          description: >-
            RECUSADO com 422 phone_not_accepted: o telefone é sempre digitado
            pelo titular no widget, nunca enviado na criação.
        policy:
          type: object
          properties:
            allow_pep:
              type: boolean
            allow_betting_ban:
              type: boolean
            ubo_max_paid_nodes:
              type: integer
              minimum: 1
              maximum: 10
          minProperties: 1
          description: >-
            Override de política por sessão (whitelist fechada; chave
            desconhecida vira 400, nunca é ignorada em silêncio).
        return_url:
          type: string
          maxLength: 2048
          description: >-
            Para onde o WIDGET manda o titular quando a verificação termina.
            Existe para o caso do APLICATIVO NATIVO: no iOS o caminho
            recomendado é abrir a verificação no navegador do sistema, e sem
            destino de volta o titular acaba e fica preso lá. Aceita https ou o
            esquema PRÓPRIO do seu aplicativo (meubanco://kyc/pronto); os
            esquemas que o navegador interpreta sozinho (javascript:, data:,
            blob:, file:, intent: e afins) são recusados com 400, e http também
            (a volta depois de uma verificação de identidade não desce para
            texto claro). A volta NÃO carrega desfecho, score nem nada da
            verificação, nem em query nem em fragmento: um redirecionamento no
            browser do titular é forjável por quem controla o aparelho, então
            quem conta o que aconteceu é o WEBHOOK, e o seu aplicativo pergunta
            ao próprio backend. Ausente = ninguém é redirecionado. Destino https
            só vale com o host na lista de destinos de retorno do ambiente
            (painel, Organização): comparação exata de host, curinga só na forma
            *.seudominio.com.br cobrindo um nível, e host fora da lista é
            recusado com 400 return_url_not_allowed. Sem lista no ambiente, a
            sessão é criada mas o titular termina na tela de conclusão, sem
            redirecionamento. O esquema próprio do aplicativo não tem host a
            comparar e não depende da lista.
        credit_relationship:
          type: string
          enum:
            - existing
            - prospective
          description: >-
            A relação da sua empresa com o titular, como a Lei 12.414 (art. 15)
            exige para consultar dado de crédito: existing (você já mantém
            relação comercial ou de crédito com ele) ou prospective (vai iniciar
            uma). OBRIGATÓRIO quando o flow contém módulo de crédito (422
            credit_relationship_required) e RECUSADO quando não contém (422
            credit_relationship_not_supported). A finalidade da consulta vem do
            flow (credit_purpose, art. 7) e fica carimbada na sessão; a sua
            conta precisa ter CNPJ cadastrado e verificado, porque ela é o
            consulente (422 credit_requester_unverified). A consulta de crédito
            só sai com a identidade APROVADA, e cada consulta fica registrada
            com finalidade, relação e consulente. Os módulos de crédito estão em
            "Em breve".
        auth:
          type: object
          properties:
            code_challenge:
              type: string
              pattern: ^[A-Za-z0-9_-]{43}$
            code_challenge_method:
              type: string
              enum:
                - S256
            intent:
              type: string
              enum:
                - sign_in
                - sign_up
                - auto
            state:
              type: string
              maxLength: 512
            locale:
              type: string
              enum:
                - pt-BR
                - en-US
                - es-ES
          required:
            - code_challenge
            - code_challenge_method
          description: >-
            Bloco da autenticação do usuário final (login, cadastro e MFA no
            widget), só em flow com a autenticação ligada: em flow sem ela é
            recusado com 422 user_auth_not_enabled, e o flow com ela exige o
            bloco (422 auth_required). code_challenge é o desafio PKCE S256 que
            o SEU backend gera (RFC 7636): 43 caracteres base64url do SHA-256 do
            code_verifier, que fica só com você e volta na troca do código;
            code_challenge_method aceita só S256. intent é o que o usuário veio
            fazer (sign_in, sign_up ou auto, o padrão), state é um valor opaco
            seu de até 512 caracteres devolvido no fim, e locale é o idioma dos
            códigos e avisos (pt-BR, en-US ou es-ES). Sessão de login não é
            renovada (409 auth_session_not_renewable): crie outra. Ela não é
            recusada por saldo e não conta no teto de tentativas da referência.
        monitoring:
          type: object
          properties:
            enabled:
              type: boolean
          required:
            - enabled
        transaction:
          type: object
          properties:
            type:
              type: string
              enum:
                - deposit
                - withdraw
                - bet
                - bet_profit
                - bet_loss
                - transfer
                - payment
                - settlement
                - reversal
            amount_cents:
              type: integer
            currency:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
            external_id:
              type: string
              minLength: 1
              maxLength: 512
            occurred_at:
              type: string
            status:
              type: string
              enum:
                - pending
                - confirmed
                - failed
            settles:
              type: string
              minLength: 1
              maxLength: 512
            method:
              type: string
              enum:
                - pix
                - ted
                - boleto
                - card
                - internal
            counterparty_ref:
              type: string
              minLength: 1
              maxLength: 512
            device:
              type: object
              properties:
                ip:
                  type: string
                  maxLength: 64
                  description: Espaços nas pontas são removidos antes da validação.
                fingerprint:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: Espaços nas pontas são removidos antes da validação.
            cash:
              type: boolean
            counterparty_country:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
            direction:
              type: string
              enum:
                - in
                - out
            instrument:
              type: object
              properties:
                kind:
                  type: string
                  enum:
                    - card
                    - account
                    - wallet
                    - pix_key
                ref:
                  type: string
                  minLength: 1
                  maxLength: 512
              required:
                - kind
                - ref
          required:
            - type
            - amount_cents
          description: >-
            Evento de transação (ingestão). Só é aceito em flow que contenha um
            módulo consumidor de transação; hoje esse módulo é o Gate
            Transacional (`transaction_gate`). Flow sem consumidor responde 422
            transaction_not_supported. É exclusivo com `transactions` (os dois
            juntos = 422 transaction_conflict), external_id é obrigatório
            (idempotência durável) e, sem id natural, derive
            sha256(reference_id|type|amount_cents|occurred_at|posição); a
            resposta 201 traz a forma de ingestão (id ing_ + contadores) e, em
            flow com gate síncrono, o veredito do gate.
        transactions:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                enum:
                  - deposit
                  - withdraw
                  - bet
                  - bet_profit
                  - bet_loss
                  - transfer
                  - payment
                  - settlement
                  - reversal
              amount_cents:
                type: integer
              currency:
                type: string
                description: Espaços nas pontas são removidos antes da validação.
              external_id:
                type: string
                minLength: 1
                maxLength: 512
              occurred_at:
                type: string
              status:
                type: string
                enum:
                  - pending
                  - confirmed
                  - failed
              settles:
                type: string
                minLength: 1
                maxLength: 512
              method:
                type: string
                enum:
                  - pix
                  - ted
                  - boleto
                  - card
                  - internal
              counterparty_ref:
                type: string
                minLength: 1
                maxLength: 512
              device:
                type: object
                properties:
                  ip:
                    type: string
                    maxLength: 64
                    description: Espaços nas pontas são removidos antes da validação.
                  fingerprint:
                    type: string
                    minLength: 1
                    maxLength: 255
                    description: Espaços nas pontas são removidos antes da validação.
              cash:
                type: boolean
              counterparty_country:
                type: string
                description: Espaços nas pontas são removidos antes da validação.
              direction:
                type: string
                enum:
                  - in
                  - out
              instrument:
                type: object
                properties:
                  kind:
                    type: string
                    enum:
                      - card
                      - account
                      - wallet
                      - pix_key
                  ref:
                    type: string
                    minLength: 1
                    maxLength: 512
                required:
                  - kind
                  - ref
            required:
              - type
              - amount_cents
          minItems: 1
          description: >-
            Lote de eventos (ingestão). Mesma condição do campo `transaction`:
            flow sem módulo consumidor responde 422 transaction_not_supported.
            Aceita 1..teto INGEST_MAX_BATCH (acima = 422 batch_too_large com o
            teto no corpo) e reenviar o mesmo lote não duplica nem cobra de novo
            (dedupe por external_id). ATENÇÃO: o lote é PROIBIDO quando o flow
            contém um gate síncrono, e o Gate Transacional (`transaction_gate`)
            é um, então nesse flow use `transaction` (singular): o lote responde
            422 batch_not_supported_for_gate, porque um gate decide UM pagamento
            e um lote não teria veredito.
        account_event:
          type: object
          properties:
            type:
              type: string
              enum:
                - login
                - login_failed
                - signup
                - password_change
                - recovery
                - email_change
                - sensitive_action
                - reward_claim
                - withdrawal
            external_id:
              type: string
              minLength: 1
              maxLength: 255
              description: Espaços nas pontas são removidos antes da validação.
            action:
              type: string
              maxLength: 64
              description: Espaços nas pontas são removidos antes da validação.
            occurred_at:
              type: string
            device:
              type: object
              properties:
                ip:
                  type: string
                  minLength: 3
                  maxLength: 45
                  description: Espaços nas pontas são removidos antes da validação.
                fingerprint:
                  type: string
                  maxLength: 512
                  description: Espaços nas pontas são removidos antes da validação.
                user_agent:
                  type: string
                  maxLength: 512
                  description: Espaços nas pontas são removidos antes da validação.
              required:
                - ip
            document_hash: {}
            disposable_email:
              type: boolean
          required:
            - type
            - external_id
            - device
        expected_address:
          type: object
          properties:
            cep:
              type: string
              pattern: ^\d{5}-?\d{3}$
              description: Espaços nas pontas são removidos antes da validação.
            uf:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
              enum:
                - AC
                - AL
                - AP
                - AM
                - BA
                - CE
                - DF
                - ES
                - GO
                - MA
                - MT
                - MS
                - MG
                - PA
                - PB
                - PR
                - PE
                - PI
                - RJ
                - RN
                - RS
                - RO
                - RR
                - SC
                - SP
                - SE
                - TO
          required:
            - cep
        esignature:
          type: object
          properties:
            document_sha256:
              type: string
              pattern: ^[0-9a-f]{64}$
              description: Espaços nas pontas são removidos antes da validação.
          required:
            - document_sha256
          description: >-
            Bloco do módulo `esignature` (assinatura eletrônica avançada):
            document_sha256 é o SHA-256, em hex, do documento que o titular vai
            assinar. O documento em si NUNCA é enviado: você guarda os bytes, a
            UNIFOKAL amarra o hash à verificação aprovada e devolve o dossiê
            assinado (Ed25519) no webhook. OBRIGATÓRIO quando o flow contém o
            módulo (422 esignature_document_required) e RECUSADO quando não
            contém (422 esignature_not_supported); chave desconhecida dentro do
            bloco vira 400 unknown_esignature_key, nunca é ignorada em silêncio.
            Em produção, o módulo vende quando a chave da plataforma de produção
            está publicada; até lá, criar flow de produção com ele responde 422.
        consultation_authorization:
          type: object
          properties:
            text_sha256:
              type: string
              pattern: ^[0-9a-f]{64}$
              description: Espaços nas pontas são removidos antes da validação.
            text_version:
              type: string
              minLength: 1
              maxLength: 64
              pattern: ^[\x21-\x7e](?:[\x20-\x7e]{0,62}[\x21-\x7e])?$
              description: Espaços nas pontas são removidos antes da validação.
            scope:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
              enum:
                - scr
          required:
            - text_sha256
            - text_version
            - scope
          description: >-
            Bloco do módulo `authorization_custody` (custódia da autorização de
            consulta ao SCR): text_sha256 é o SHA-256, em hex, do texto de
            autorização que o titular leu e aceitou; text_version é a versão
            desse texto no seu sistema; scope é o escopo autorizado (hoje, scr).
            O texto em si NUNCA é enviado. Com a verificação aprovada, a
            UNIFOKAL registra a autorização amarrada à identidade verificada e
            ao consentimento da sessão, com prova assinada que qualquer um
            confere sem depender da UNIFOKAL, e guarda o registro por cinco anos
            contados da última consulta que você registra no painel. OBRIGATÓRIO
            quando o flow contém o módulo (422
            consultation_authorization_required) e RECUSADO quando não contém
            (422 consultation_authorization_not_supported); chave desconhecida
            dentro do bloco vira 400 unknown_consultation_authorization_key. Em
            produção, o módulo vende quando a chave de custódia de produção está
            publicada; até lá, criar flow de produção com ele responde 422.
        foreign_entity:
          type: object
          properties:
            lei:
              type: string
              pattern: ^[0-9A-Z]{18}[0-9]{2}$
              description: Espaços nas pontas são removidos antes da validação.
            name:
              type: string
              minLength: 1
              maxLength: 200
              description: Espaços nas pontas são removidos antes da validação.
            country:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
              enum:
                - AE
                - AO
                - AR
                - AT
                - AU
                - BE
                - BO
                - BR
                - CA
                - CH
                - CL
                - CN
                - CO
                - CR
                - CZ
                - DE
                - DK
                - EC
                - ES
                - FI
                - FR
                - GB
                - GR
                - GY
                - HK
                - HR
                - IE
                - IL
                - IT
                - JP
                - KR
                - KY
                - LU
                - MX
                - MZ
                - NL
                - 'NO'
                - NZ
                - PA
                - PE
                - PL
                - PT
                - PY
                - SE
                - SG
                - SR
                - US
                - UY
                - VE
                - VG
            beneficial_owners:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                    minLength: 1
                    maxLength: 200
                    description: Espaços nas pontas são removidos antes da validação.
                required:
                  - name
              maxItems: 20
          oneOf:
            - required:
                - lei
            - required:
                - name
          dependentRequired:
            name:
              - country
          dependentSchemas:
            lei:
              not:
                anyOf:
                  - required:
                      - country
          description: >-
            Bloco do módulo `kyb_foreign` (empresa de fora do Brasil, sem CNPJ):
            lei é o identificador de entidade legal da empresa (ISO 17442, 20
            caracteres); sem ele, name e country (a mesma lista fechada de
            países do painel), nunca os dois. beneficial_owners é opcional: até
            20 beneficiários finais pessoa natural que você declara, só pelo
            nome, triados nas listas de sanções e de pessoas expostas
            politicamente; os nomes ficam cifrados e nunca vão ao índice de
            entidades. OBRIGATÓRIO quando o flow contém o módulo (422
            foreign_entity_required) e RECUSADO quando não contém (422
            foreign_entity_not_supported); LEI com dígito de controle errado
            vira 422 foreign_entity_lei_invalid; chave desconhecida vira 400
            unknown_foreign_entity_key. O módulo está pausado no catálogo ("Em
            breve").
        act:
          type: object
          properties:
            kind:
              type: string
              minLength: 1
              maxLength: 40
              description: Espaços nas pontas são removidos antes da validação.
            summary:
              type: string
              minLength: 1
              maxLength: 140
              description: Espaços nas pontas são removidos antes da validação.
            amount_cents:
              type: integer
              minimum: 0
              maximum: 9007199254740991
            currency:
              type: string
              enum:
                - BRL
            counterparty:
              type: string
              minLength: 1
              maxLength: 80
              description: Espaços nas pontas são removidos antes da validação.
            external_id:
              type: string
              minLength: 1
              maxLength: 255
              description: >-
                O id único do ato no seu sistema, de 1 a 255 caracteres: letras,
                dígitos e . _ : @ -, sem começar por - nem @ (fora disso, 422
                reference_id_charset), e nunca dado pessoal: é o id do pedido no
                seu sistema. Caractere invisível e espaço nas pontas não contam:
                a forma comparada e carimbada é a canônica. É a chave de
                idempotência da sessão com ato, por 600 segundos: repetir o
                mesmo external_id devolve a MESMA sessão, com Idempotent-Replay:
                true, e com corpo diferente é 422 idempotency_key_reuse.
          required:
            - kind
            - external_id
        pix_device:
          type: object
          properties:
            fingerprint:
              type: string
              minLength: 8
              maxLength: 512
              description: Espaços nas pontas são removidos antes da validação.
            label:
              type: string
              minLength: 1
              maxLength: 64
              description: Espaços nas pontas são removidos antes da validação.
            platform:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
              enum:
                - ios
                - android
                - web
          required:
            - fingerprint
        session_monitoring:
          type: object
          properties:
            window_hours:
              type: integer
              minimum: 1
            require_geolocation: {}
          required:
            - window_hours
        pld_profile:
          type: object
          properties:
            monthly_capacity_cents:
              type: integer
              minimum: 0
              maximum: 1000000000000
            net_worth_cents:
              type: integer
              minimum: 0
              maximum: 1000000000000
            declared_at:
              type: string
            activity_code:
              type: object
              properties:
                kind:
                  type: string
                  enum:
                    - cnae
                    - cbo
                code:
                  type: string
                  pattern: ^[0-9./-]{2,12}$
                  description: Espaços nas pontas são removidos antes da validação.
              required:
                - kind
                - code
            legal_nature_code:
              type: string
              pattern: ^[0-9]{3}-?[0-9]$
              description: Espaços nas pontas são removidos antes da validação.
            residence_country:
              type: string
              description: Espaços nas pontas são removidos antes da validação.
            pep_declared:
              type: boolean
            public_servant:
              type: boolean
            minor:
              type: boolean
            parliamentary_amendment_account:
              type: boolean
            relationship_started_at:
              type: string
            risk_class:
              type: string
              enum:
                - low
                - medium
                - high
      required:
        - flow_id
        - reference_id
    VerificationSession:
      type: object
      description: >-
        Resposta 201 da criação. O resultado da verificação chega no seu
        webhook. Os blocos opcionais decision e policy aparecem só quando o flow
        tem o módulo que os produz.
      required:
        - id
        - flow_id
        - environment
        - reference_id
        - status
        - expires_at
        - created_at
        - modules
        - livemode
        - expires_in
      properties:
        id:
          $ref: '#/components/schemas/VerificationSessionId'
        flow_id:
          $ref: '#/components/schemas/FlowId'
        environment:
          type: string
          enum:
            - sandbox
            - production
        reference_id:
          type:
            - string
            - 'null'
        origin_tag:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$
          maxLength: 64
          description: >-
            Origem da jornada que você mandou na criação, EXATA. Ausente quando
            você não mandou.
        metadata:
          type: object
          maxProperties: 20
          propertyNames:
            pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,39}$
            maxLength: 40
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 500
          description: >-
            O metadata que você mandou na criação, EXATO. Ausente quando você
            não mandou.
        status:
          type: string
          enum:
            - requires_input
            - processing
            - completed
            - expired
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        modules:
          type: array
          items:
            type: string
          description: >-
            Módulos do flow. Valor novo pode surgir sem subir a versão (adição
            compatível): ignore o que não conhecer.
        livemode:
          type: boolean
        expires_in:
          type: integer
          description: >-
            Segundos até a sessão expirar. Crie a sessão com a pessoa presente;
            nunca em lote.
        decision:
          type: object
          description: >-
            Veredito do evento de conta, na mesma chamada. Presente só quando o
            flow tem o módulo conta.
          required:
            - verdict
            - risk_level
            - risk_score
            - reasons
            - verification_id
            - step_up
          additionalProperties: false
          properties:
            verdict:
              type: string
              enum:
                - allow
                - step_up
                - deny
            risk_level:
              type: string
              enum:
                - low
                - medium
                - high
              description: 'Faixa do risco do evento, em ordem crescente: low, medium, high.'
            risk_score:
              type: integer
              minimum: 0
              maximum: 100
              multipleOf: 20
              deprecated: true
              description: >-
                Obsoleto: use risk_level. Risco do evento, arredondado em faixas
                de 20; sai na versão 2.
            reasons:
              type: array
              items:
                type: string
              description: >-
                Códigos dos motivos do veredito. A lista é aberta: valor novo
                pode surgir sem subir a versão, então trate o que não conhecer
                como informativo.
            verification_id:
              oneOf:
                - $ref: '#/components/schemas/VerificationId'
                - type: 'null'
              description: >-
                Verificação que guarda o dossiê deste evento. Null quando
                nenhuma verificação foi criada para ele.
            step_up:
              type: object
              required:
                - available
                - session_id
              additionalProperties: false
              description: >-
                Step-up com prova de humano. Sem step-up configurado no flow,
                available é false e session_id é null. Configurado e com
                veredito step_up, available true traz em session_id a sessão de
                PROVA (monte o widget com ela), válida por 600 segundos, e
                factor diz o fator escolhido pela conta. Com available false,
                reason diz por quê; com gate_budget_exceeded, aplique o seu
                próprio desafio. O teto diário do módulo conta só signup e
                recovery: acima dele o evento é avaliado e não é cobrado, a
                prova não abre e reason sai step_up_rate_limited (aplique o seu
                desafio); os demais tipos são sempre cobrados e se limitam pelo
                orçamento da chave. login vai só depois de a credencial
                conferir; a tentativa que falhou vai como login_failed, que
                nunca abre prova. O resultado chega no webhook da sessão de
                prova, com data.step_up_of.
              properties:
                available:
                  type: boolean
                session_id:
                  type:
                    - string
                    - 'null'
                expires_at:
                  type: string
                  format: date-time
                factor:
                  type: string
                  enum:
                    - passkey
                    - face_reauth
                    - passkey_e_face_reauth
                reason:
                  type: string
                  enum:
                    - no_factor_enrolled
                    - factor_locked
                    - step_up_not_configured
                    - step_up_flow_not_live
                    - insufficient_credit
                    - step_up_rate_limited
                    - step_up_unavailable
                    - step_up_closed
                    - step_up_sandbox
                    - gate_budget_exceeded
        policy:
          type: object
          description: >-
            Política efetiva de compliance com que a sessão nasceu, e a origem
            de cada valor. Presente só quando o flow tem pep_sanctions ou
            betting_barred; cada par aparece só para o módulo que o flow tem.
          additionalProperties: false
          properties:
            allow_pep:
              type: boolean
            allow_pep_source:
              type: string
              enum:
                - flow
                - session
                - session_inherited
              description: >-
                flow: o padrão do flow. session: o policy que você mandou nesta
                criação. session_inherited: herdado da sessão renovada.
            allow_betting_ban:
              type: boolean
            allow_betting_ban_source:
              type: string
              enum:
                - flow
                - session
                - session_inherited
                - unresolved
              description: >-
                Os três primeiros como em allow_pep_source. unresolved: a
                política não pôde ser lida, e a verificação fica pendente em vez
                de aprovar.
    IngestResponse:
      type: object
      description: >-
        Resposta 201 quando o corpo traz transaction/transactions (ingestão).
        Nunca traz score, regra ou limiar; o bloco ingest é SEMPRE o mesmo para
        qualquer módulo consumidor.
      required:
        - id
        - status
        - ingest
      properties:
        id:
          $ref: '#/components/schemas/IngestBatchId'
        status:
          type: string
          const: consumed
        ingest:
          type: object
          required:
            - batch_id
            - accepted
            - duplicated
            - batch_size
            - first_seen_at
          properties:
            batch_id:
              $ref: '#/components/schemas/IngestBatchId'
            accepted:
              type: integer
              description: Eventos gravados nesta chamada.
            duplicated:
              type: integer
              description: >-
                Eventos já vistos (dedupe durável por external_id): retry não
                duplica nem cobra de novo.
            batch_size:
              type: integer
            first_seen_at:
              type: string
              format: date-time
        transaction_gate:
          type: object
          required:
            - verdict
          description: >-
            Veredito do Gate Transacional, na mesma chamada. Presente só em flow
            com o módulo `transaction_gate` e em evento avaliável; nesse caso
            vem sempre, e para o mesmo external_id vem sempre igual. O
            external_id é único por organização e ambiente, e o veredito dele
            também: repetir o mesmo external_id em outro flow devolve o veredito
            já dado, sem reavaliar. No sandbox o veredito é determinístico pelos
            dois últimos dígitos de amount_cents (13 = deny, 07 = step_up, o
            resto = allow), sai só do valor (não consulta perfil nem lista de
            bloqueio) e não grava nada.
          properties:
            verdict:
              type: string
              enum:
                - allow
                - step_up
                - deny
            step_up:
              type: object
              required:
                - available
              description: >-
                Step-up com prova de humano. Presente só quando o flow tem
                step-up configurado e o veredito é step_up. Com available true,
                session_id é a sessão de PROVA (monte o widget com ela, como na
                criação de sessão), válida por 600 segundos, e factor diz o
                fator escolhido pela conta (passkey, face_reauth ou os dois).
                Com available false, reason diz por quê, e você aplica o seu
                próprio desafio. A lista de reason é a mesma do gate de conta:
                gate_budget_exceeded nunca sai desta ingestão. O resultado da
                prova chega no webhook da sessão de prova. Repetir o mesmo
                external_id devolve o mesmo step-up enquanto o pedido estiver
                aberto, e step_up_closed depois.
              properties:
                available:
                  type: boolean
                session_id:
                  $ref: '#/components/schemas/VerificationSessionId'
                expires_at:
                  type: string
                  format: date-time
                factor:
                  type: string
                  enum:
                    - passkey
                    - face_reauth
                    - passkey_e_face_reauth
                reason:
                  type: string
                  enum:
                    - no_factor_enrolled
                    - factor_locked
                    - step_up_not_configured
                    - step_up_flow_not_live
                    - insufficient_credit
                    - step_up_rate_limited
                    - step_up_unavailable
                    - step_up_closed
                    - step_up_sandbox
                    - gate_budget_exceeded
    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
    VerificationSessionId:
      type: string
      pattern: ^vs_[0-9A-HJKMNP-TV-Z]{26}$
      description: >-
        Id da sessão de verificação (vs_ + ULID). É o único valor que vai ao
        browser: o widget monta só com ele.
    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.
    VerificationId:
      type: string
      pattern: ^ver_[0-9A-HJKMNP-TV-Z]{26}$
      description: Id da verificação (ver_ + ULID), o identificador que o webhook carrega.
    IngestBatchId:
      type: string
      pattern: ^ing_[0-9A-HJKMNP-TV-Z]{26}$
      description: >-
        Id do LOTE de ingestão (ing_ + ULID). NÃO é credencial de nada: usado
        como Bearer, dá 401.
  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.