{
  "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://unifokal.com/docs"
    }
  },
  "externalDocs": {
    "description": "Documentação completa em https://unifokal.com/docs. Briefing para agentes de IA em https://unifokal.com/llms.txt.",
    "url": "https://unifokal.com/docs"
  },
  "servers": [
    {
      "url": "https://api.unifokal.com/v1"
    }
  ],
  "paths": {
    "/verification-sessions": {
      "post": {
        "operationId": "createVerificationSession",
        "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.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "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: validation_error, unknown_policy_key, unknown_monitoring_key, unknown_consultation_authorization_key, unknown_foreign_entity_key, unknown_beneficial_owner_key, unknown_act_key. Ramifique pelo campo error, nunca pela mensagem.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "enum": [
                            "validation_error",
                            "unknown_policy_key",
                            "unknown_monitoring_key",
                            "unknown_consultation_authorization_key",
                            "unknown_foreign_entity_key",
                            "unknown_beneficial_owner_key",
                            "unknown_act_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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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, 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",
                            "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Códigos estáveis: flow_not_live, email_not_accepted, phone_not_accepted, idempotency_key_reuse, policy_module_not_in_flow, policy_ubo_cap_above_flow, transaction_conflict, transaction_required, transaction_not_supported, session_not_supported, batch_too_large, batch_not_supported_for_gate, external_id_required, amount_too_large, currency_not_supported, event_too_old, reference_id_charset, pii_shaped_value, origin_tag_not_supported, account_event_required, account_event_not_supported, account_event_ip_not_public, assinatura_document_required, assinatura_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_consulente_unverified, 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, 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",
                            "email_not_accepted",
                            "phone_not_accepted",
                            "idempotency_key_reuse",
                            "policy_module_not_in_flow",
                            "policy_ubo_cap_above_flow",
                            "transaction_conflict",
                            "transaction_required",
                            "transaction_not_supported",
                            "session_not_supported",
                            "batch_too_large",
                            "batch_not_supported_for_gate",
                            "external_id_required",
                            "amount_too_large",
                            "currency_not_supported",
                            "event_too_old",
                            "reference_id_charset",
                            "pii_shaped_value",
                            "origin_tag_not_supported",
                            "account_event_required",
                            "account_event_not_supported",
                            "account_event_ip_not_public",
                            "assinatura_document_required",
                            "assinatura_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_consulente_unverified",
                            "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",
                            "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/webhook-events": {
      "get": {
        "operationId": "listUndeliveredWebhookEvents",
        "summary": "Lista os webhooks NÃO confirmados (falha definitiva de entrega), para redisparo",
        "security": [
          {
            "secretKey": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Valores acima de 20 são reduzidos a 20 por página."
          },
          {
            "name": "resource_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Filtra pelos eventos de UM recurso. Sem ele, a listagem responde exatamente como sempre respondeu."
          }
        ],
        "responses": {
          "200": {
            "description": "Entregas com falha DEFINITIVA no ambiente da chave (sandbox e produção nunca se cruzam).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEventList"
                }
              }
            }
          },
          "400": {
            "description": "Códigos estáveis: validation_error. Ramifique pelo campo error, nunca pela mensagem.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "enum": [
                            "validation_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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: credential_type_not_allowed, organization_suspended, test_key_used_in_production. 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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/webhook-events/replay": {
      "post": {
        "operationId": "replayWebhookEvents",
        "summary": "Redispara os webhooks de até 20 verificações ou recursos",
        "description": "Teto de 30 chamadas por minuto. Reenvia o corpo salvo byte a byte ao destino atual.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplayRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado por item pedido (verificação ou recurso); itens podem falhar individualmente (campo error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplayResponse"
                }
              }
            }
          },
          "400": {
            "description": "Códigos estáveis: validation_error. Ramifique pelo campo error, nunca pela mensagem.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "enum": [
                            "validation_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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: credential_type_not_allowed, organization_suspended, test_key_used_in_production. 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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/verification-links": {
      "post": {
        "operationId": "createVerificationLink",
        "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.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "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: validation_error, unknown_policy_key, unknown_act_key. Ramifique pelo campo error, nunca pela mensagem.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "enum": [
                            "validation_error",
                            "unknown_policy_key",
                            "unknown_act_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, 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",
                            "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Códigos estáveis: flow_not_live, email_not_accepted, phone_not_accepted, expires_in_too_long, environment_mismatch, policy_module_not_in_flow, policy_ubo_cap_above_flow, session_not_supported, transaction_required, assinatura_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, foreign_entity_required. Ramifique pelo campo error, nunca pela mensagem.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "enum": [
                            "flow_not_live",
                            "email_not_accepted",
                            "phone_not_accepted",
                            "expires_in_too_long",
                            "environment_mismatch",
                            "policy_module_not_in_flow",
                            "policy_ubo_cap_above_flow",
                            "session_not_supported",
                            "transaction_required",
                            "assinatura_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",
                            "foreign_entity_required"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/capabilities": {
      "get": {
        "operationId": "getCapabilities",
        "summary": "Catálogo vivo de módulos e preços; com sk_, o contrato efetivo da sua organização",
        "description": "Credencial OPCIONAL. Sem credencial: catálogo geral com o preço-base público (cacheável). Com Bearer sk_: a MESMA URL devolve o contrato da sua organização (preço efetivo com override de contrato, ambiente e livemode da chave; sem cache HTTP). O estado available/coming_soon é o MESMO que a criação de flow enforça: módulo em coming_soon é recusado com 422 em flow de produção, e aceito em sandbox.",
        "security": [
          {},
          {
            "secretKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo de capacidades. context.authenticated diz qual dos dois modos respondeu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          },
          "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: credential_type_not_allowed, organization_suspended, test_key_used_in_production. 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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "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"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "verification.completed": {
      "post": {
        "operationId": "receiveVerificationWebhook",
        "summary": "A entrega assinada com o resultado da verificação (a fonte da verdade)",
        "description": "Verifique a assinatura HMAC SHA-256 do header X-IDSAAS-Signature (formato t=<ts>,v1=<hex>, janela de 300s) antes de confiar no corpo. Responda 2xx rápido e deduplique pelo id do evento; sem 2xx, a entrega re-tenta com backoff exponencial, 3 tentativas no total, ao longo de cerca de 15 minutos; passada a janela a entrega para e o resgate é o replay (operação replayWebhookEvents). Crédito e aprovação no seu sistema só a partir do webhook VERIFICADO, nunca da resposta síncrona.",
        "parameters": [
          {
            "name": "X-IDSAAS-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Assinatura HMAC (pode trazer mais de um v1 durante rotação de segredo)."
          },
          {
            "name": "x-idsaas-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Tipo do evento (verification.<tipo>)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmação de recebimento. Qualquer 2xx conta; processar depois, responder já."
          }
        }
      }
    },
    "pld.alert.created": {
      "post": {
        "operationId": "receivePldAlertWebhook",
        "summary": "O aviso assinado de que o monitoramento de PLD/FT selecionou uma operação ou situação",
        "description": "Mesma assinatura (X-IDSAAS-Signature), mesmos headers e mesma escada de retentativas dos eventos de verificação; deduplique pelo id do evento. Chega no webhook do flow que tem o módulo pld_monitor. A carga é mínima e SIGILOSA (Lei 9.613/1998, art. 11, II): não dê ciência ao titular nem a terceiros. O caso completo, a disposição e a exportação para o Siscoaf ficam no painel; a decisão de comunicar ao Coaf continua sendo da sua organização.",
        "parameters": [
          {
            "name": "X-IDSAAS-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Assinatura HMAC (pode trazer mais de um v1 durante rotação de segredo)."
          },
          {
            "name": "x-idsaas-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "pld.alert.created"
            },
            "description": "Tipo do evento."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PldAlertWebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmação de recebimento. Qualquer 2xx conta; processar depois, responder já."
          }
        }
      }
    },
    "passkey.bound": {
      "post": {
        "operationId": "receivePasskeyBoundWebhook",
        "summary": "A passkey do titular virou ativa",
        "description": "Mesma assinatura (X-IDSAAS-Signature), mesmos headers e mesma escada de retentativas dos eventos de verificação; deduplique pelo id do evento. Chega no webhook do flow da sessão que vinculou a chave. É o aviso de vínculo de autenticador que o titular deve receber por canal independente: repasse a ele.",
        "parameters": [
          {
            "name": "X-IDSAAS-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Assinatura HMAC (pode trazer mais de um v1 durante rotação de segredo)."
          },
          {
            "name": "x-idsaas-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "passkey.bound"
            },
            "description": "Tipo do evento."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasskeyWebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmação de recebimento. Qualquer 2xx conta; processar depois, responder já."
          }
        }
      }
    },
    "passkey.revoked": {
      "post": {
        "operationId": "receivePasskeyRevokedWebhook",
        "summary": "A passkey do titular foi revogada",
        "description": "Mesma assinatura (X-IDSAAS-Signature), mesmos headers e mesma escada de retentativas dos eventos de verificação; deduplique pelo id do evento. Chega no webhook do flow da sessão que vinculou a chave, inclusive quando a revogação veio do painel ou do apagamento do titular.",
        "parameters": [
          {
            "name": "X-IDSAAS-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Assinatura HMAC (pode trazer mais de um v1 durante rotação de segredo)."
          },
          {
            "name": "x-idsaas-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "passkey.revoked"
            },
            "description": "Tipo do evento."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasskeyWebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmação de recebimento. Qualquer 2xx conta; processar depois, responder já."
          }
        }
      }
    },
    "act.rejected": {
      "post": {
        "operationId": "receiveActRejectedWebhook",
        "summary": "O titular recusou o ato na página de confirmação",
        "description": "Mesma assinatura (X-IDSAAS-Signature), mesmos headers e mesma escada de retentativas dos eventos de verificação; deduplique pelo id do evento. Chega no webhook do flow da sessão de prova. A recusa não é autenticada: pare o ato, sem tratar como prova de fraude.",
        "parameters": [
          {
            "name": "X-IDSAAS-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Assinatura HMAC (pode trazer mais de um v1 durante rotação de segredo)."
          },
          {
            "name": "x-idsaas-event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "act.rejected"
            },
            "description": "Tipo do evento."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActRejectedWebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmação de recebimento. Qualquer 2xx conta; processar depois, responder já."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "secretKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Chave secreta sk_ 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."
      }
    },
    "schemas": {
      "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 sk_."
      },
      "VerificationId": {
        "type": "string",
        "pattern": "^ver_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Id da verificação (ver_ + ULID), o identificador que o webhook carrega."
      },
      "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."
          },
          "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."
          },
          "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."
          },
          "credit_relationship": {
            "type": "string",
            "enum": [
              "mantem",
              "pretende_manter"
            ],
            "description": "A relação da sua empresa com o titular, como a Lei 12.414 (art. 15) exige para consultar dado de crédito: mantem (você já mantém relação comercial ou de crédito com ele) ou pretende_manter (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_consulente_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\"."
          },
          "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 (`transacao`). 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 (`transacao`) é 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"
                ]
              },
              "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",
              "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"
            ]
          },
          "assinatura": {
            "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 `assinatura` (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 assinatura_document_required) e RECUSADO quando não contém (422 assinatura_not_supported); chave desconhecida dentro do bloco vira 400, nunca é ignorada em silêncio. O módulo está pausado no catálogo (\"Em breve\")."
          },
          "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 `custodia_autorizacao` (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. O módulo está pausado no catálogo (\"Em breve\")."
          },
          "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",
                  "HR",
                  "IE",
                  "IL",
                  "IT",
                  "JP",
                  "KR",
                  "LU",
                  "MX",
                  "MZ",
                  "NL",
                  "NO",
                  "NZ",
                  "PE",
                  "PL",
                  "PT",
                  "PY",
                  "SE",
                  "SR",
                  "US",
                  "UY",
                  "VE"
                ]
              },
              "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
              }
            },
            "description": "Bloco do módulo `kyb_estrangeira` (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; com forma de dado pessoal, 422 pii_shaped_value). 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": [
                  "baixo",
                  "medio",
                  "alto"
                ]
              }
            }
          }
        },
        "required": [
          "flow_id",
          "reference_id"
        ]
      },
      "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."
          },
          "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 sk_ 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; com forma de dado pessoal, 422 pii_shaped_value). 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."
          },
          "assinatura": {
            "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"
        ]
      },
      "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."
      },
      "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"
              }
            }
          },
          "transacao": {
            "type": "object",
            "required": [
              "verdict"
            ],
            "description": "Veredito do Gate Transacional, na mesma chamada. Presente só em flow com o módulo `transacao` 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 dos outros gates: 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"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "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."
          },
          "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_score",
              "reasons",
              "verification_id",
              "step_up"
            ],
            "additionalProperties": false,
            "properties": {
              "verdict": {
                "type": "string",
                "enum": [
                  "allow",
                  "step_up",
                  "deny"
                ]
              },
              "risk_score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100,
                "multipleOf": 20,
                "description": "Risco do evento, arredondado em faixas de 20."
              },
              "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,
                "properties": {
                  "available": {
                    "type": "boolean"
                  },
                  "session_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          },
          "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_sancoes ou impedidos_apostar; 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."
              }
            }
          }
        }
      },
      "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_."
      },
      "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 por sk_ 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"
          }
        }
      },
      "UndeliveredWebhookEvent": {
        "type": "object",
        "description": "Um webhook cujo ciclo de entrega terminou SEM confirmação 2xx do seu servidor.",
        "required": [
          "event_type",
          "verification_id",
          "resource_id",
          "reference_id",
          "target_url",
          "status",
          "status_code",
          "attempts",
          "failed_at"
        ],
        "properties": {
          "event_type": {
            "type": "string",
            "enum": [
              "completed",
              "blocked",
              "failed",
              "pending",
              "monitoring",
              "test",
              "pix_device_revoked",
              "subject_erased",
              "verification_link_claimed",
              "passkey_bound",
              "passkey_revoked",
              "act_rejected",
              "pld_alert_created"
            ]
          },
          "verification_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VerificationId"
              },
              {
                "type": "null"
              }
            ]
          },
          "resource_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identificador do recurso deste evento quando ele não pertence a uma verificação. É a chave aceita pelo replay."
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_url": {
            "type": "string",
            "description": "Host do destino desta entrega. O endereco completo do seu webhook nao volta em resposta de leitura: ele aparece so para quem administra os endpoints, no painel."
          },
          "status": {
            "type": "string",
            "const": "error"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WebhookEventList": {
        "type": "object",
        "required": [
          "data",
          "page",
          "totalPages",
          "totalItems"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UndeliveredWebhookEvent"
            }
          },
          "page": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          },
          "totalItems": {
            "type": "integer"
          }
        }
      },
      "ReplayRequest": {
        "type": "object",
        "description": "Os dois campos são opcionais e ao menos um é obrigatório. O teto vale para a SOMA dos dois.",
        "properties": {
          "verification_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "minItems": 1,
            "maxItems": 20,
            "uniqueItems": true,
            "description": "Até 20 ids de verificação por chamada (somados aos resource_ids)."
          },
          "resource_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 128
            },
            "minItems": 1,
            "maxItems": 20,
            "uniqueItems": true,
            "description": "Para os eventos que não pertencem a uma verificação: o resource_id que veio no próprio evento. Até 20 por chamada (somados aos verification_ids)."
          }
        }
      },
      "ReplayResult": {
        "type": "object",
        "required": [
          "resent",
          "http_status"
        ],
        "properties": {
          "verification_id": {
            "type": "string",
            "description": "Presente quando o item foi pedido por verificação."
          },
          "resource_id": {
            "type": "string",
            "description": "Presente quando o item foi pedido por recurso."
          },
          "resent": {
            "type": "boolean"
          },
          "http_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": "string",
            "description": "Presente só quando este item falhou (ex.: nothing_to_replay)."
          }
        }
      },
      "ReplayResponse": {
        "type": "object",
        "required": [
          "results",
          "resent",
          "failed"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReplayResult"
            }
          },
          "resent": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        }
      },
      "CapabilityModule": {
        "type": "object",
        "required": [
          "module",
          "group",
          "status",
          "unit_cents",
          "is_addon",
          "requires"
        ],
        "properties": {
          "module": {
            "type": "string",
            "description": "Nome do módulo (o mesmo usado nos flows)."
          },
          "group": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "coming_soon"
            ],
            "description": "available = vendável hoje; coming_soon = preço publicado, venda ainda não liberada. Um módulo pode sair de coming_soon sem aviso prévio: confira este catálogo antes de montar o flow."
          },
          "unit_cents": {
            "type": "integer",
            "minimum": 0,
            "description": "Preço unitário em centavos de BRL. Sem credencial, o preço-base de vitrine; com sk_, o preço efetivo do contrato da sua organização."
          },
          "is_addon": {
            "type": "boolean"
          },
          "requires": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Módulos que este exige no mesmo flow (o mesmo grafo que a API enforça com 422)."
          },
          "requires_any_of": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 1
            },
            "description": "Grupos de alternativas: de CADA grupo, pelo menos um módulo precisa estar no mesmo flow, além de todos os de requires (a API enforça com 422 module_dependency_violation). Exemplo: a inscrição estadual pede o CNPJ lido e UM dos três dados da empresa. Ausente quando o módulo não tem alternativa."
          },
          "decision": {
            "type": "string",
            "enum": [
              "decisive",
              "informational"
            ],
            "description": "O que você leva por este preço. decisive = o resultado participa do veredito da verificação (portão que reprova ou segura, ou peso no score). informational = o módulo ENTREGA DADO e não julga: é resolvido depois da decisão e não altera aprovação, reprovação nem score. Ausente nas linhas que não são módulo de flow (unidades de metering)."
          }
        }
      },
      "CapabilitiesContext": {
        "type": "object",
        "required": [
          "authenticated",
          "environment",
          "livemode",
          "pricing"
        ],
        "properties": {
          "authenticated": {
            "type": "boolean"
          },
          "environment": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "sandbox",
              "production",
              null
            ]
          },
          "livemode": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "pricing": {
            "type": "string",
            "enum": [
              "list",
              "contract"
            ],
            "description": "list = preço-base público; contract = preço efetivo da organização da chave."
          }
        }
      },
      "Capabilities": {
        "type": "object",
        "required": [
          "api_version",
          "spec_url",
          "docs_url",
          "llms_url",
          "webhook_schema_version",
          "context",
          "modules"
        ],
        "properties": {
          "api_version": {
            "type": "string",
            "const": "v1"
          },
          "spec_url": {
            "type": "string",
            "format": "uri"
          },
          "docs_url": {
            "type": "string",
            "format": "uri"
          },
          "llms_url": {
            "type": "string",
            "format": "uri"
          },
          "webhook_schema_version": {
            "type": "integer",
            "const": 1
          },
          "context": {
            "$ref": "#/components/schemas/CapabilitiesContext"
          },
          "modules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CapabilityModule"
            }
          }
        }
      },
      "WebhookData": {
        "type": "object",
        "description": "Conteúdo da verificação decidida. Campo novo pode surgir sem subir schema_version (adição compatível): o parser tem que ignorar chave desconhecida. check_details é omitido quando o endpoint está em modo minimal; truncated marca corpo podado acima do teto (a decisão nunca é podada).",
        "required": [
          "object",
          "id",
          "verification_id",
          "flow_id",
          "reference_id",
          "status",
          "score",
          "risk_level",
          "recommendation",
          "decision_reason",
          "reason_code",
          "environment",
          "completed_at",
          "saldoUsado",
          "saldoRestante",
          "checks"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "verification"
          },
          "id": {
            "$ref": "#/components/schemas/VerificationId"
          },
          "verification_id": {
            "$ref": "#/components/schemas/VerificationId"
          },
          "flow_id": {
            "$ref": "#/components/schemas/FlowId"
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "blocked",
              "failed",
              "review",
              "consent_declined"
            ]
          },
          "score": {
            "type": [
              "number",
              "null"
            ]
          },
          "risk_level": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "low",
              "medium",
              "high",
              null
            ]
          },
          "recommendation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "approve",
              "review",
              "decline",
              null
            ]
          },
          "decision_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason_code": {
            "type": [
              "object",
              "null"
            ],
            "description": "Motivo da decisão em forma legível. Carrega code (categoria), module, secondary, subject_code, catalog_version e subject_message. Desde 11 de setembro de 2026 carrega também reasons, uma lista em que cada item separa a evidência (code, o mesmo vocabulário de decision_reason) do efeito dela no desfecho (outcome_effect: blocked, review ou info), com o módulo de origem, o grupo (aspect: document, biometrics, data_validation, fraud_signals ou channel) e dois textos em português, display_pt e action_pt; e aspects, o índice desses códigos pelos cinco grupos, sempre com as cinco chaves."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "saldoUsado": {
            "type": "integer",
            "description": "Centavos debitados por ESTA verificação (sandbox: valor fixo)."
          },
          "saldoRestante": {
            "type": "integer",
            "description": "Saldo após o débito, estável entre reentregas."
          },
          "checks": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Resumo semântico por área (sem PII). Presente também no modo minimal."
          },
          "check_details": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "module",
                "passed",
                "outcome",
                "score",
                "reason"
              ],
              "properties": {
                "module": {
                  "type": "string"
                },
                "passed": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "outcome": {
                  "type": "string"
                },
                "score": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "data": {
                  "type": "object",
                  "description": "O bloco do módulo, com o formato próprio de cada um. No módulo pep_sancoes ele segue o schema ScreeningCheckData."
                },
                "completeness": {
                  "type": "string",
                  "enum": [
                    "partial"
                  ],
                  "description": "Aparece só quando o módulo respondeu e parte do dado não veio: o bloco data traz o que veio, com null no que faltou. Ausente, o bloco está completo."
                }
              },
              "additionalProperties": true
            }
          },
          "blocklist_face_match": {
            "type": [
              "object",
              "null"
            ],
            "description": "Evidência do portão de rosto da blocklist, só quando ele moveu a decisão."
          },
          "origin_tag": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$",
            "maxLength": 64,
            "description": "A origem da jornada que você mandou na criação da sessão, EXATA. Ausente quando você não mandou; presente também no modo minimal; nunca no evento subject_erased."
          },
          "act": {
            "type": "object",
            "description": "O ato da sessão de prova (confirmação fora de banda, ato direto ou step-up): tipo, digest act-v1 e o external_id do ato. O digest é o mesmo do link de confirmação. Ausente quando a verificação não tem ato.",
            "required": [
              "kind",
              "digest",
              "external_id"
            ],
            "properties": {
              "kind": {
                "type": "string"
              },
              "digest": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$"
              },
              "external_id": {
                "type": "string"
              }
            }
          },
          "purpose": {
            "type": "string",
            "enum": [
              "confirmation"
            ],
            "description": "Presente só quando a verificação nasceu de um link de confirmação fora de banda."
          },
          "step_up_of": {
            "type": "object",
            "description": "Presente só na verificação da sessão de prova que um gate abriu (o step-up com prova de humano): source é o gate que pediu a prova, verification_id é a verificação do gate (a do pagamento ou do evento de conta) e event_type é o tipo do evento. É por ele que você liga o desfecho da prova ao pagamento que esperava. A verificação do gate não muda por causa da prova. Presente também no modo minimal.",
            "required": [
              "source",
              "verification_id",
              "event_type"
            ],
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "transacao",
                  "conta"
                ]
              },
              "verification_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "event_type": {
                "type": "string"
              }
            }
          },
          "attestation": {
            "type": "object",
            "required": [
              "issued"
            ],
            "description": "Presente só na verificação de um flow com o módulo atestado_humano, também no modo minimal. Com issued true, sd_jwt é o atestado SD-JWT VC compacto (format dc+sd-jwt) para a pessoa guardar ou apresentar, com vct, kid, issued_at, expires_at e disclosable (as afirmações que ela pode revelar uma a uma). Se a chave de assinatura não estava disponível na entrega, sd_jwt vem null com reason, e a reemissão pelo painel devolve o token. Com issued false, reason diz por que não houve emissão, e nenhum outro campo vem. O atestado só é emitido em produção.",
            "properties": {
              "issued": {
                "type": "boolean"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "format": {
                "type": "string",
                "const": "dc+sd-jwt"
              },
              "sd_jwt": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "vct": {
                "type": "string"
              },
              "kid": {
                "type": "string"
              },
              "issued_at": {
                "type": "string",
                "format": "date-time"
              },
              "expires_at": {
                "type": "string",
                "format": "date-time"
              },
              "disclosable": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "sub",
                    "prova_de_vida",
                    "unica_no_servico",
                    "maioridade"
                  ]
                }
              }
            }
          },
          "billing": {
            "type": "object",
            "required": [
              "waived"
            ],
            "properties": {
              "waived": {
                "type": "string",
                "enum": [
                  "above_volume",
                  "no_credit"
                ]
              }
            },
            "description": "Só em alerta do monitoramento transacional entregue SEM cobrança. waived diz o porquê: above_volume é um alerta de severidade alta entregue acima do volume contratado, e no_credit é um alerta de severidade alta entregue sem saldo. Alerta de severidade alta nunca é retido por volume nem por falta de saldo. Ausente em toda verificação cobrada normalmente; presente também no modo minimal."
          },
          "truncated": {
            "type": "boolean"
          },
          "truncated_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "ScreeningCheckData": {
        "type": "object",
        "description": "Bloco data do check pep_sancoes (PEP e listas restritivas). Campo novo pode surgir sem subir schema_version: ignore chave desconhecida.",
        "required": [
          "pep",
          "pep_status",
          "flagged",
          "matched_by",
          "reason",
          "hits",
          "hits_truncated",
          "aggregates",
          "policy",
          "dataset_versions"
        ],
        "properties": {
          "pep": {
            "type": "boolean",
            "description": "Há ao menos um hit de lista de PEP."
          },
          "pep_status": {
            "type": [
              "object",
              "null"
            ],
            "description": "Resumo de PEP derivado dos hits. As janelas contam do FIM DO EXERCÍCIO da função, não do fim da carência. null em verificação decidida antes de o bloco existir.",
            "required": [
              "current",
              "last_1y",
              "last_3y",
              "last_5y",
              "roles"
            ],
            "properties": {
              "current": {
                "type": "boolean",
                "description": "Há hit de PEP com a função em exercício."
              },
              "last_1y": {
                "type": "boolean",
                "description": "Há hit de PEP que deixou a função há no máximo 1 ano."
              },
              "last_3y": {
                "type": "boolean",
                "description": "Há hit de PEP que deixou a função há no máximo 3 anos."
              },
              "last_5y": {
                "type": "boolean",
                "description": "Há hit de PEP que deixou a função há no máximo 5 anos."
              },
              "roles": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "A descrição da função como a fonte oficial publica, só de hit por documento e forte. Hit por nome nunca traz cargo."
              }
            }
          },
          "flagged": {
            "type": "boolean"
          },
          "matched_by": {
            "type": "string",
            "enum": [
              "document",
              "name",
              "none"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "hits": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "source",
                "list",
                "matched_by",
                "strong",
                "similarity",
                "precision",
                "listed_at",
                "left_at",
                "current",
                "entry_ref"
              ],
              "properties": {
                "source": {
                  "type": "string",
                  "description": "Id ESTÁVEL da lista (por exemplo cgu_pep, cgu_ceis, ofac_sdn). É o campo para programar contra: não muda quando o rótulo muda."
                },
                "list": {
                  "type": "string",
                  "description": "Rótulo legível da lista (por exemplo PEP, CEIS, OFAC SDN). Pode mudar de texto."
                },
                "matched_by": {
                  "type": "string",
                  "enum": [
                    "document",
                    "name"
                  ]
                },
                "strong": {
                  "type": "boolean"
                },
                "similarity": {
                  "type": "number"
                },
                "precision": {
                  "type": "number"
                },
                "listed_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date"
                },
                "left_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date",
                  "description": "Fim do vínculo como a fonte publica. Na lista de PEP é o fim do período em que a pessoa é considerada politicamente exposta."
                },
                "current": {
                  "type": "boolean",
                  "description": "O vínculo está vigente na data da consulta."
                },
                "entry_ref": {
                  "type": "string",
                  "description": "Referência opaca e estável da entrada da lista."
                },
                "exercise_end": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date",
                  "description": "Só em hit de PEP: o fim do exercício da função. null quando a função está em exercício."
                },
                "pep_in_carency": {
                  "type": "boolean",
                  "description": "Só em hit de PEP: deixou a função e ainda é considerada politicamente exposta (carência de 5 anos)."
                },
                "details": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "description": "Só em hit por documento e forte: os campos públicos da fonte que explicam o hit (por exemplo funcao e orgao), como texto. As chaves variam por lista."
                }
              }
            }
          },
          "hits_truncated": {
            "type": "boolean"
          },
          "aggregates": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "pep": {
                "type": "integer"
              },
              "sanctions": {
                "type": "integer"
              },
              "strong": {
                "type": "integer"
              },
              "by_list": {
                "type": "object",
                "additionalProperties": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "description": "Contagem por rótulo de lista; null quando a lista não foi consultada nesta verificação."
              },
              "windows": {
                "type": "object",
                "deprecated": true,
                "description": "Janelas por data de inclusão na lista, somando PEP e sanção. Continua no payload; prefira sanction_windows e pep_windows.",
                "properties": {
                  "d30": {
                    "type": "integer"
                  },
                  "d90": {
                    "type": "integer"
                  },
                  "d180": {
                    "type": "integer"
                  },
                  "d365": {
                    "type": "integer"
                  },
                  "d1825": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  }
                }
              },
              "sanction_windows": {
                "type": "object",
                "description": "Janelas (d30, d90, d180, d365, d1825, total) só dos hits de sanção, pela data de inclusão na lista.",
                "properties": {
                  "d30": {
                    "type": "integer"
                  },
                  "d90": {
                    "type": "integer"
                  },
                  "d180": {
                    "type": "integer"
                  },
                  "d365": {
                    "type": "integer"
                  },
                  "d1825": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  }
                }
              },
              "pep_windows": {
                "type": "object",
                "description": "Janelas (d30, d90, d180, d365, d1825, total) só dos hits de PEP, pela data de inclusão na lista.",
                "properties": {
                  "d30": {
                    "type": "integer"
                  },
                  "d90": {
                    "type": "integer"
                  },
                  "d180": {
                    "type": "integer"
                  },
                  "d365": {
                    "type": "integer"
                  },
                  "d1825": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  }
                }
              },
              "pep_in_carency": {
                "type": "integer"
              },
              "is_pep": {
                "type": "boolean"
              },
              "has_sanctions_br": {
                "type": "boolean",
                "description": "Há hit de lista de sanção publicada por órgão brasileiro."
              },
              "has_sanctions_intl": {
                "type": "boolean",
                "description": "Há hit de lista de sanção publicada por organismo ou governo estrangeiro."
              },
              "worst_severity": {
                "type": "string",
                "enum": [
                  "no_hit",
                  "weak",
                  "strong"
                ],
                "description": "A pior zona entre os hits."
              }
            }
          },
          "policy": {
            "type": [
              "object",
              "null"
            ]
          },
          "dataset_versions": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      },
      "WebhookEnvelope": {
        "type": "object",
        "description": "Envelope entregue ao seu endpoint. schema_version só sobe em mudança INCOMPATÍVEL (que também exigiria /v2); adição de campo mantém a versão. Corpo acima de 262144 bytes chega podado e marcado com truncated.",
        "required": [
          "id",
          "schema_version",
          "event",
          "livemode",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Id estável do evento (deduplique por ele): evt_<verification_id>_<tipo>, com sufixo _rN em reemissão manual."
          },
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "event": {
            "type": "string",
            "enum": [
              "verification.completed",
              "verification.blocked",
              "verification.failed",
              "verification.pending",
              "verification.monitoring",
              "verification.test",
              "verification.pix_device_revoked",
              "verification.subject_erased",
              "verification.verification_link_claimed"
            ]
          },
          "livemode": {
            "type": "boolean"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookData"
          }
        }
      },
      "PldAlertWebhookData": {
        "type": "object",
        "description": "Aviso de que o monitoramento de PLD/FT SELECIONOU uma operação ou situação (Circular BCB 3.978/2020, art. 39). Carga mínima e sigilosa (Lei 9.613/1998, art. 11, II): nenhuma evidência, valor ou operação; o caso completo fica no painel, com acesso registrado. Não dê ciência ao titular. Campo novo pode surgir sem subir schema_version: ignore chave desconhecida.",
        "required": [
          "object",
          "id",
          "reference_id",
          "severity",
          "kind",
          "items",
          "basis",
          "selected_at",
          "deadlines",
          "dashboard_path"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "pld_alert"
          },
          "id": {
            "type": "string",
            "pattern": "^plda_[0-9A-HJKMNP-TV-Z]{26}$"
          },
          "reference_id": {
            "type": "string",
            "description": "O identificador do titular no SEU sistema (o reference_id da ingestão)."
          },
          "severity": {
            "type": "string",
            "enum": [
              "media",
              "alta",
              "critica"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "analise",
              "objetiva",
              "csnu"
            ],
            "description": "analise: pede análise no prazo; objetiva: comunicação objetiva da norma; csnu: sanção do CSNU, providência imediata."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Itens da norma que a seleção atende."
          },
          "basis": {
            "type": "string",
            "description": "Fundamento legal por extenso."
          },
          "selected_at": {
            "type": "string",
            "format": "date-time"
          },
          "deadlines": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "due_at"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "due_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "dashboard_path": {
            "type": "string",
            "description": "Caminho do alerta no painel da UNIFOKAL."
          }
        }
      },
      "PasskeyWebhookData": {
        "type": "object",
        "description": "A passkey do titular virou ATIVA (passkey.bound) ou foi REVOGADA (passkey.revoked). O passkey.bound e o aviso de vínculo de autenticador que a NIST SP 800-63B-4 (4.1.2.1) manda dar ao titular por canal independente: repasse a ele. Campo novo pode surgir sem subir schema_version: ignore chave desconhecida.",
        "required": [
          "object",
          "passkey_id",
          "reference_id",
          "environment",
          "status",
          "bound_by",
          "assurance",
          "backup_eligible",
          "backup_state",
          "device_bound",
          "activated_at",
          "verification_id"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "passkey"
          },
          "passkey_id": {
            "type": "string",
            "pattern": "^spk_[0-9A-HJKMNP-TV-Z]{26}$"
          },
          "reference_id": {
            "type": "string",
            "description": "O identificador do titular no SEU sistema."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ]
          },
          "bound_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "cadastro",
              "presenca",
              "chave_existente",
              "reprova",
              null
            ],
            "description": "Como a chave foi vinculada: no cadastro, numa prova de presença, sobre chave existente ou na recuperação por nova prova."
          },
          "assurance": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "identidade",
              "presenca",
              null
            ],
            "description": "A garantia que a chave carrega: a identidade provada no vínculo, ou só a presença."
          },
          "backup_eligible": {
            "type": "boolean",
            "description": "A chave pode ser sincronizada (passkey de nuvem)."
          },
          "backup_state": {
            "type": "boolean",
            "description": "A chave está sincronizada agora."
          },
          "device_bound": {
            "type": "boolean",
            "description": "A chave vive só no aparelho (o contrário de backup_eligible)."
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "verification_id": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^ver_[0-9A-HJKMNP-TV-Z]{26}$",
            "description": "A verificação que vinculou a chave."
          },
          "recovery": {
            "type": "boolean",
            "description": "Só no passkey.bound: o vínculo veio de uma recuperação por nova prova (bound_by reprova)."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Só no passkey.revoked."
          },
          "revoke_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "painel",
              "apagamento",
              "recuperacao",
              null
            ],
            "description": "Só no passkey.revoked: revogada pelo painel, pelo apagamento do titular ou pela recuperação."
          }
        }
      },
      "PasskeyWebhookEnvelope": {
        "type": "object",
        "description": "Envelope do passkey.bound e do passkey.revoked. Mesma assinatura, headers e escada de retentativas dos eventos de verificação.",
        "required": [
          "id",
          "schema_version",
          "event",
          "livemode",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Id estável do evento (deduplique por ele): evt_<passkey_id>_passkey_bound ou evt_<passkey_id>_passkey_revoked."
          },
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "event": {
            "type": "string",
            "enum": [
              "passkey.bound",
              "passkey.revoked"
            ]
          },
          "livemode": {
            "type": "boolean"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/PasskeyWebhookData"
          }
        }
      },
      "ActRejectedWebhookData": {
        "type": "object",
        "description": "O titular disse \"Não reconheço este pedido\" na página do ato. A recusa NÃO é autenticada (o titular não precisa do fator para dizer não): trate como sinal rápido para parar o ato, nunca como prova de fraude. Campo novo pode surgir sem subir schema_version: ignore chave desconhecida.",
        "required": [
          "object",
          "session_id",
          "reference_id",
          "act_kind",
          "act_external_id",
          "act_digest",
          "rejected_at",
          "authenticated"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "act"
          },
          "session_id": {
            "type": "string",
            "pattern": "^vs_[0-9A-HJKMNP-TV-Z]{26}$"
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "O identificador do titular no SEU sistema."
          },
          "act_kind": {
            "type": "string",
            "description": "O kind do ato (confirmacao_de_presenca quando a sessão não trouxe ato)."
          },
          "act_external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "O external_id do ato no seu sistema."
          },
          "act_digest": {
            "type": [
              "string",
              "null"
            ],
            "description": "O digest act-v1 calculado na criação, o mesmo que volta no verification.completed (data.act.digest)."
          },
          "purpose": {
            "type": "string",
            "const": "confirmation",
            "description": "Só na recusa de um link de confirmação fora de banda."
          },
          "rejected_at": {
            "type": "string",
            "format": "date-time"
          },
          "authenticated": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "ActRejectedWebhookEnvelope": {
        "type": "object",
        "description": "Envelope do act.rejected. Mesma assinatura, headers e escada de retentativas dos eventos de verificação.",
        "required": [
          "id",
          "schema_version",
          "event",
          "livemode",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Id estável do evento (deduplique por ele): evt_<session_id>_act_rejected."
          },
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "event": {
            "type": "string",
            "const": "act.rejected"
          },
          "livemode": {
            "type": "boolean"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/ActRejectedWebhookData"
          }
        }
      },
      "PldAlertWebhookEnvelope": {
        "type": "object",
        "description": "Envelope do aviso pld.alert.created. Mesma assinatura, headers e escada de retentativas dos eventos de verificação.",
        "required": [
          "id",
          "schema_version",
          "event",
          "livemode",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Id estável do evento (deduplique por ele): evt_<alert_id>_pld_alert_created."
          },
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "event": {
            "type": "string",
            "const": "pld.alert.created"
          },
          "livemode": {
            "type": "boolean"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/PldAlertWebhookData"
          }
        }
      }
    }
  }
}
