> ## 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 link hospedado de verificação (o titular abre a jornada sem você montar o widget)

> Para quem NÃO vai montar o widget: a UNIFOKAL hospeda a página e você entrega a url ao titular (e-mail, WhatsApp, QR). O link vive HORAS (a sessão vive 900s e só nasce no resgate), o token vlt_ volta em claro uma única vez e o link não cobra nada: quem cobra é a verificação que nascer do resgate. Sem Idempotency-Key de propósito: selar o corpo guardaria o segredo no banco; repetir a chamada só cria outro link, que expira sozinho.



## OpenAPI

````yaml /api-reference/openapi.json post /verification-links
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-links:
    post:
      summary: >-
        Cria um link hospedado de verificação (o titular abre a jornada sem você
        montar o widget)
      description: >-
        Para quem NÃO vai montar o widget: a UNIFOKAL hospeda a página e você
        entrega a url ao titular (e-mail, WhatsApp, QR). O link vive HORAS (a
        sessão vive 900s e só nasce no resgate), o token vlt_ volta em claro uma
        única vez e o link não cobra nada: quem cobra é a verificação que nascer
        do resgate. Sem Idempotency-Key de propósito: selar o corpo guardaria o
        segredo no banco; repetir a chamada só cria outro link, que expira
        sozinho.
      operationId: createVerificationLink
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateLinkRequest'
      responses:
        '201':
          description: Link criado. Entregue a url ao titular; o token não é reexibido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationLink'
        '400':
          description: >-
            Códigos estáveis: invalid_json, validation_error,
            unknown_policy_key, unknown_act_key, unknown_esignature_key.
            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_act_key
                          - unknown_esignature_key
        '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
        '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: confirmation_unavailable. Ramifique pelo campo
            error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - confirmation_unavailable
        '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, expires_in_too_long,
            environment_mismatch, policy_module_not_in_flow,
            policy_ubo_cap_above_flow, pep_override_not_enabled,
            session_not_supported, transaction_required,
            esignature_document_required, consultation_authorization_required,
            passkey_not_enrolled, passkey_bind_needs_authentication,
            passkey_bind_needs_reproof, act_not_accepted, act_required,
            act_external_id_required, act_external_id_not_accepted,
            expires_in_too_long_for_confirmation,
            confirmation_requires_human_proof, no_factor_enrolled,
            act_hostile_char, act_kind_invalid, act_summary_invalid,
            act_counterparty_invalid, act_amount_invalid, act_currency_invalid,
            reference_id_charset, pii_shaped_value, metadata_too_large,
            foreign_entity_required, account_event_required,
            content_removed_by_notice. 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
                          - expires_in_too_long
                          - environment_mismatch
                          - policy_module_not_in_flow
                          - policy_ubo_cap_above_flow
                          - pep_override_not_enabled
                          - session_not_supported
                          - transaction_required
                          - esignature_document_required
                          - consultation_authorization_required
                          - passkey_not_enrolled
                          - passkey_bind_needs_authentication
                          - passkey_bind_needs_reproof
                          - act_not_accepted
                          - act_required
                          - act_external_id_required
                          - act_external_id_not_accepted
                          - expires_in_too_long_for_confirmation
                          - confirmation_requires_human_proof
                          - no_factor_enrolled
                          - act_hostile_char
                          - act_kind_invalid
                          - act_summary_invalid
                          - act_counterparty_invalid
                          - act_amount_invalid
                          - act_currency_invalid
                          - reference_id_charset
                          - pii_shaped_value
                          - metadata_too_large
                          - foreign_entity_required
                          - account_event_required
                          - content_removed_by_notice
        '429':
          description: >-
            Códigos estáveis: confirmation_rate_limited, rate_limited. Ramifique
            pelo campo error, nunca pela mensagem.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - properties:
                      error:
                        enum:
                          - confirmation_rate_limited
                          - rate_limited
      security:
        - secretKey: []
components:
  schemas:
    CreateLinkRequest:
      type: object
      properties:
        flow_id:
          type: string
          minLength: 1
          description: >-
            Id do flow que define os módulos da verificação (prefixo flow_). O
            flow precisa estar live no ambiente da credencial.
        reference_id:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            Seu identificador do titular/da tentativa. Volta EM CLARO na
            resposta e no webhook da verificação: 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.
        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. A sessão que o
            link abre herda o mesmo metadata.
        email:
          description: >-
            RECUSADO com 422 email_not_accepted: a página hospedada PEDE o
            e-mail ao titular e dispara o código em seguida, igual ao telefone.
            Nunca envie o endereço na emissão.
        phone:
          description: >-
            RECUSADO com 422 phone_not_accepted: o telefone é sempre digitado
            pelo titular no widget, nunca enviado na emissão.
        expires_in:
          type: integer
          minimum: 60
          description: >-
            Validade do LINK em segundos (mínimo 60; acima do teto da casa é 422
            expires_in_too_long, nunca um clamp silencioso). Não confunda com a
            vida da sessão, que só nasce no resgate.
        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, validado JÁ NA EMISSÃO (a sessão do
            resgate herda). Política de módulo que o flow não tem é 422
            policy_module_not_in_flow, e o teto ubo_max_paid_nodes só APERTA o
            do flow (acima dele é 422 policy_ubo_cap_above_flow).
        environment:
          type: string
          enum:
            - sandbox
            - production
          description: >-
            SÓ para a superfície de painel. Com a chave secreta o ambiente É a
            chave: mandar um diferente é 422 environment_mismatch (aceitar e
            ignorar faria você acreditar que trocou de ambiente).
        purpose:
          type: string
          enum:
            - verification
            - confirmation
          description: >-
            verification (padrão, o link de sempre) ou confirmation: o pedido
            pontual de CONFIRMAÇÃO FORA DE BANDA. A confirmação exige o bloco
            act, um flow com passkey ou face_reauth e uma conta com o fator (422
            no_factor_enrolled); vale até 600 segundos (padrão 600; acima é 422
            expires_in_too_long_for_confirmation); no máximo um pedido aberto
            por pessoa (o novo substitui o anterior, que é revogado) e cinco por
            hora (429 confirmation_rate_limited, com Retry-After). O link é
            transporte, não fator: quem confirma é a passkey com verificação do
            usuário ou a reautenticação facial com prova de vida.
        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, obrigatório pela API e
                recusado pelo painel (lá o id do ato é o do próprio link), 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. Entra no digest act-v1 e volta em
                data.act.external_id.
          required:
            - kind
          description: >-
            O ATO que a pessoa confirma, só com purpose confirmation (no link
            comum é 422 act_not_accepted): kind (até 40) e summary (até 140)
            obrigatórios; amount_cents, currency BRL e counterparty (até 80)
            opcionais; external_id, o id único do ato no seu sistema,
            obrigatório pela API. O digest act-v1 é calculado uma vez na criação
            e volta igual no verification.completed (data.act.digest) e no
            act.rejected.
        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
      required:
        - flow_id
        - reference_id
    VerificationLink:
      type: object
      description: >-
        Resposta 201 da criação do link hospedado. O campo token (vlt_) é
        devolvido EM CLARO uma única vez e nunca é reexibido: guarde-o ou
        entregue a url ao titular na mesma resposta.
      required:
        - id
        - environment
        - flow_id
        - reference_id
        - contact_masked
        - created_via
        - created_by_member_id
        - status
        - expires_at
        - opened_at
        - consumed_at
        - session_id
        - revoked_at
        - created_at
        - purpose
        - act_kind
        - act_digest
        - url
        - token
        - expires_in
        - livemode
      properties:
        id:
          $ref: '#/components/schemas/VerificationLinkId'
        environment:
          type: string
          enum:
            - sandbox
            - production
        flow_id:
          $ref: '#/components/schemas/FlowId'
        reference_id:
          type:
            - string
            - 'null'
        contact_masked:
          type:
            - string
            - 'null'
          description: >-
            E-mail MASCARADO, e só quando o flow verifica e-mail: o endereço em
            claro fica cifrado no repouso e nunca volta por aqui.
        created_via:
          type: string
          enum:
            - api
            - dashboard
        created_by_member_id:
          type:
            - string
            - 'null'
          description: >-
            Membro que emitiu pelo painel. Emissão pela chave secreta não tem
            membro: null.
        status:
          type: string
          enum:
            - pending
            - opened
            - consumed
            - expired
            - revoked
          description: >-
            Estado do link. `consumed` significa que alguém resgatou e a sessão
            nasceu (veja session_id).
        expires_at:
          type: string
          format: date-time
        opened_at:
          type:
            - string
            - 'null'
          format: date-time
        consumed_at:
          type:
            - string
            - 'null'
          format: date-time
        session_id:
          type:
            - string
            - 'null'
          description: Sessão criada no resgate do link. Null até alguém abrir e resgatar.
        revoked_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        url:
          type: string
          format: uri
          description: >-
            A página hospedada, com o token embutido. É o que você entrega ao
            titular.
        token:
          type: string
          description: >-
            Segredo do link (vlt_), em claro UMA vez. Guardamos só o hash: não
            há como reexibir.
        purpose:
          type: string
          enum:
            - verification
            - confirmation
          description: >-
            verification = o link de sempre; confirmation = o pedido de
            confirmação fora de banda.
        act_kind:
          type:
            - string
            - 'null'
          description: Tipo do ato da confirmação. Null no link comum.
        act_digest:
          type:
            - string
            - 'null'
          pattern: ^[0-9a-f]{64}$
          description: >-
            Digest act-v1 (sha256 hex) do ato, calculado uma vez na criação. O
            mesmo volta em data.act.digest do verification.completed. Null no
            link comum.
        expires_in:
          type: integer
          description: Segundos de validade do link (não da sessão).
        livemode:
          type: boolean
    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
    VerificationLinkId:
      type: string
      pattern: ^vl_[0-9A-HJKMNP-TV-Z]{26}$
      description: >-
        Id do link hospedado (vl_ + ULID). NÃO é o segredo: o segredo é o token
        vlt_.
    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.
  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.