{
  "openapi": "3.0.3",
  "info": {
    "title": "Sentinex Risk Crypto - API de KYT",
    "description": "Contrato público da integração de KYT (monitoramento de transações) da\nSentinex Risk Crypto. Ele lista só as operações que a API de produção serve\na uma integração autenticada por chave de API.\n\n## Autenticação\n\nToda operação deste contrato usa a chave de API da empresa no header\n`X-API-Key`. A chave identifica a empresa e carrega os escopos; nenhum header\nextra de empresa é necessário. Em produção, o grupo `/vasp` aceita apenas chave\ndo ambiente `live`. A chave é criada no painel, na área de desenvolvedores, por\num administrador da empresa.\n\n## Recomendação e decisão\n\n`POST /vasp/screenings` devolve a recomendação `ALLOW`, `REVIEW`, `HOLD` ou\n`DENY`, com o detalhamento do score e das regras acionadas. A decisão\noperacional é do cliente e é registrada em\n`PATCH /vasp/screenings/{id}/client-decision`.\n\n## Convenções da integração\n\nCampos JSON usam `snake_case`. Enums de recomendação, ação e estado de\nnegócio usam caixa alta. Datas e horas usam RFC 3339 em UTC. `amount`\nrepresenta quantidade decimal do ativo; campos `_brl` representam reais,\nnão centavos; campos `_ms` representam milissegundos; scores KYT usam 0 a\n100. Listagens `/vasp` usam `limit`, com default 50 e teto 200; alertas\npaginam com `offset` (as leituras de ledger também, fora do contrato do\ncliente KYT), e clientes com o cursor `cursor_before`, devolvido em\n`next_cursor`. Depois da autenticação valem o limite da empresa, 2.000 por\nminuto somando todas as chaves e usuários, e o limite por minuto da API key,\nque usa 60 por minuto quando a credencial não define outro valor e é\ninformado por headers. O laudo KYT acrescenta 120 emissões por hora,\ncontadas por IP de origem. Rota inexistente sob `/vasp`, com chave válida,\nresponde `404` com `error.code` `route_not_found`.\n\n## Versão\n\n`info.version` é a versão deste contrato do cliente. Ela é própria: não acompanha\na especificação interna nem os SDKs, e só sobe quando este contrato muda. A\npolítica de versão e o changelog estão em\nhttps://docs-crypto.sentinexrisk.com/concepts/versionamento-e-changelog/.\n\n`info.x-snx-congelamento` diz em que fase a linha está. Com `pre-integracao`, ainda\nnão há integrador em produção, e uma operação que não serve ao produto KYT pode sair\ndo contrato, sempre com a operação nomeada no changelog. Quando o primeiro integrador\nrecebe a chave, o valor passa a `congelado desde dd/mm/aaaa`, e daí em diante a linha\nsó recebe mudança compatível.\n\n## Suporte\n\n- dev-support@sentinexrisk.com\n- Documentação: https://docs-crypto.sentinexrisk.com\n",
    "version": "8.5.1",
    "x-snx-congelamento": "pre-integracao",
    "termsOfService": "https://sentinexrisk.com/termos",
    "contact": {
      "name": "Sentinex Risk Technologies",
      "url": "https://sentinexrisk.com",
      "email": "dev-support@sentinexrisk.com"
    },
    "license": {
      "name": "Licença Comercial Sentinex Risk",
      "url": "https://sentinexrisk.com/licenca"
    }
  },
  "servers": [
    {
      "url": "https://crypto-api.sentinexrisk.com/api/v1",
      "description": "Produção. Os caminhos deste contrato são relativos a /api/v1."
    }
  ],
  "tags": [
    {
      "name": "VASP - Screening",
      "description": "Decisão KYT em tempo real (POST /vasp/screenings), consulta de\ndecisões e feedback 2-way. Endpoint principal da integração.\n"
    },
    {
      "name": "VASP - Clientes",
      "description": "Enrollment de clientes finais (PF/PJ) com identificadores\npseudonimizados (SHA-256) e idempotência via Idempotency-Key.\n"
    },
    {
      "name": "VASP - Carteiras",
      "description": "Enrollment de carteiras de clientes finais com screening\nfail-closed (sync ou async).\n"
    },
    {
      "name": "VASP - Alertas",
      "description": "Poll de alertas no escopo da empresa (complementa o webhook outbound)."
    },
    {
      "name": "VASP - Webhooks",
      "description": "Self-service de subscriptions de webhook outbound."
    },
    {
      "name": "VASP - Compliance",
      "description": "SARs pendentes (read-only) e relatórios DeCripto da empresa."
    }
  ],
  "paths": {
    "/vasp/customers": {
      "post": {
        "operationId": "createVASPCustomer",
        "summary": "Cadastra cliente final do VASP (PF ou PJ)",
        "description": "Enrollment de cliente final. O documento entra em `document` (CPF ou\nCNPJ em texto, que o servidor canoniza e confere) ou em `document_hash`\n(SHA-256 do documento, que continua aceito); envie um dos dois. Se\nvierem os dois e divergirem, a resposta é 422 `DOCUMENT_HASH_MISMATCH`, e\n`document` tem precedência no cálculo do índice.\n\nO servidor não guarda o documento em claro (LGPD). Com `document`, guarda\nsó um índice cego e a cifra do valor. Só com `document_hash`, o servidor\nnão tem o documento para cifrar e, por isso, não consegue exibi-lo.\n`legal_name_hash` segue em SHA-256.\n\nSuporta replay idempotente via header Idempotency-Key. Escopo exigido:\n`vasp:customers:write`.\n",
        "tags": [
          "VASP - Clientes"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VASPCustomerCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replay idempotente (mesma Idempotency-Key + mesmo corpo)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "idempotent_replay": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPCustomer"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Cliente criado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPCustomer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo JSON ilegível (texto em `error`), ou campos inválidos\n(`invalid_customer`, `invalid_client_id`, `invalid_idempotency_key`).\nRespondem `invalid_customer` o corpo sem `document` nem\n`document_hash` e o `document` com dígito verificador inválido.\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "409": {
            "description": "external_id ou Idempotency-Key já existem (customer_already_exists, KYT_IDEMPOTENCY_CONFLICT)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "422": {
            "description": "`client_id` não identifica cliente desta empresa (`client_not_found`),\nou `document` e `document_hash` vieram juntos e divergem\n(`DOCUMENT_HASH_MISMATCH`). Nada foi gravado.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro ou transação da requisição indisponível; nenhum cliente foi\ncriado. As camadas anteriores à operação também respondem 503, nada\nprocessado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listVASPCustomers",
        "summary": "Lista clientes finais da empresa (paginação por cursor)",
        "description": "Filtros não sensíveis por risk_classification e kyc_tier. Filtros\nfiscais são aceitos apenas no corpo de POST /vasp/customers/search\npara não expor identificadores em URL. A paginação é por cursor, e não\npor offset: `limit` (default 50, teto 200; valor zero ou negativo vale\n50) e `cursor_before` com o `next_cursor` da página anterior.\n`next_cursor` volta vazio quando a página traz menos itens que\n`limit`; com a página cheia ele vem preenchido, e a página seguinte\npode vir vazia. Esta rota tem rate limit anti-enumeração adicional.\nEscopo exigido: `vasp:customers:read`.\n",
        "tags": [
          "VASP - Clientes"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "cursor_before",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco retornado em next_cursor da página anterior."
          },
          {
            "name": "risk_classification",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "LOW",
                "MEDIUM",
                "HIGH",
                "CRITICAL"
              ]
            }
          },
          {
            "name": "kyc_tier",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "SIMPLIFIED",
                "STANDARD",
                "ENHANCED"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Página de clientes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VASPCustomer"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "Vazio quando a página trouxe menos itens que `limit`."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Filtro ou cursor malformado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro de clientes indisponível. As camadas anteriores à operação\ntambém respondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/customers/search": {
      "post": {
        "operationId": "searchVASPCustomers",
        "summary": "Busca clientes finais por identificador pseudonimizado",
        "description": "Aceita cpf_hash ou cnpj_hash no corpo JSON, nunca em URL. O hash\nSHA-256 sem sal é um identificador pseudonimizado e continua sendo\ntratado como dado pessoal. Escopo exigido: `vasp:customers:read`.\n",
        "tags": [
          "VASP - Clientes"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "cpf_hash": {
                    "type": "string",
                    "pattern": "^(sha256:)?[a-fA-F0-9]{64}$"
                  },
                  "cnpj_hash": {
                    "type": "string",
                    "pattern": "^(sha256:)?[a-fA-F0-9]{64}$"
                  },
                  "risk_classification": {
                    "type": "string",
                    "enum": [
                      "LOW",
                      "MEDIUM",
                      "HIGH",
                      "CRITICAL"
                    ]
                  },
                  "kyc_tier": {
                    "type": "string",
                    "enum": [
                      "SIMPLIFIED",
                      "STANDARD",
                      "ENHANCED"
                    ]
                  },
                  "limit": {
                    "type": "integer",
                    "default": 50,
                    "maximum": 200
                  },
                  "cursor_before": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Página de clientes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VASPCustomer"
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo, filtro ou cursor malformado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro de clientes indisponível. As camadas anteriores à operação\ntambém respondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/customers/{id}": {
      "get": {
        "operationId": "getVASPCustomer",
        "summary": "Busca cliente final por id",
        "description": "O id do cliente é um UUID, o mesmo devolvido por POST /vasp/customers.\nId que não é UUID não identifica cliente nenhum e responde 404.\nEscopo exigido: `vasp:customers:read`.\n",
        "tags": [
          "VASP - Clientes"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "example": "3f2a1bc0-9d8e-47f6-a5b4-c3d2e1f00a9b"
          }
        ],
        "responses": {
          "200": {
            "description": "Cliente da empresa",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPCustomer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`id` vazio ou com mais de 80 caracteres (`invalid_customer_id`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Cliente inexistente nesta empresa, inclusive quando o `id` não é UUID (texto em\n`error`). Com chave válida, `route_not_found` diz que o caminho ou o método\nnão corresponde a operação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro de clientes indisponível. As camadas anteriores à operação\ntambém respondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/customers/batch": {
      "post": {
        "operationId": "createVASPCustomersBatch",
        "summary": "Cadastra clientes finais do VASP em lote (até 1.000 por chamada)",
        "description": "Matrícula em lote de clientes finais (PF ou PJ). Cada item de `customers`\ntem o formato do corpo de `POST /vasp/customers`, inclusive `document`\n(CPF ou CNPJ em texto, que o servidor canoniza) e `document_hash`, que\ncontinua aceito. Se `document` e `document_hash` vierem juntos no mesmo\nitem e divergirem, o item é recusado com `DOCUMENT_HASH_MISMATCH`. O\nservidor não guarda o documento em claro: com `document`, guarda só um\níndice cego e a cifra do valor; só com `document_hash`, não tem o\ndocumento para cifrar e não consegue exibi-lo.\n\nCada item é processado por conta própria: um item recusado não impede os\noutros. O resultado de cada item é `CREATED` (titular novo), `UPDATED`\n(o `external_id` já existia na empresa, e o titular é atualizado pelo\npróprio `external_id`; campo ausente no item não apaga o valor anterior)\nou `REJECTED`, com o motivo de validação. `type` e `client_id` do titular\nnão mudam por esta via: item que traga `type` diferente do titular\nexistente, ou `client_id` diferente do vínculo existente (ou um\n`client_id` para titular que não tinha vínculo), volta `REJECTED` com\n`invalid_customer`. O resultado não revela se o documento já existe sob\noutro `external_id`: o mesmo documento sob `external_id` diferentes é\naceito, sem aviso e sem recusa.\n\nA resposta é `207` sempre que o lote foi lido e autorizado, mesmo que\ntodos os itens tenham sido recusados. O resultado de cada item vem em\n`results`, na ordem do envio, e os totais em `summary`.\n\nLimites. Além do limite da chave de API, esta operação tem limites\npróprios: por lote (de 1 a 1.000 titulares, e o corpo inteiro respeita o\nteto de 1 MiB da API; se o lote não couber, divida-o), por ator (a chave\nde API que envia o lote) e por empresa, e um orçamento de itens por janela\nde tempo, também por empresa. Ao esgotar qualquer um deles, a resposta é\n429, com `Retry-After`, e nada é processado.\n\nO mesmo `external_id` mais de uma vez no lote recusa o lote inteiro\n(422 `DUPLICATE_EXTERNAL_ID`), sem gravar nada. Corpo ilegível, campo\ndesconhecido na raiz do corpo e `customers` fora de 1 a 1.000 itens\ntambém respondem 422, com `INVALID_BATCH`. `Idempotency-Key` é\nobrigatória: a repetição com a mesma chave e o mesmo corpo devolve o\nresultado original, sem gravar de novo (`idempotent_replay: true`), e a\nmesma chave com corpo diferente responde 409.\n\nMatrícula básica de KYT, autenticada por API key com o escopo\n`vasp:customers:write`. Funciona com o cruzamento desligado e não exige\no adicional de cruzamento de dados do titular.\n",
        "tags": [
          "VASP - Clientes"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "lote-clientes-01",
            "description": "Obrigatória. Mesma chave + corpo igual = replay; corpo diferente = 409. O prefixo `snx-derived:` é RESERVADO ao servidor em TODAS as rotas desta API: uma `Idempotency-Key` que comece com ele é recusada com 400."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VASPCustomerBatchRequest"
              },
              "example": {
                "customers": [
                  {
                    "external_id": "cliente-0001",
                    "type": "PF",
                    "document": "123.456.789-01"
                  },
                  {
                    "external_id": "cliente-0002",
                    "type": "PJ",
                    "document": "11.222.333/0001-82",
                    "trade_name": "Empresa Exemplo"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "207": {
            "description": "Lote lido e autorizado. O status vale para o lote; o resultado de cada\nitem, criado, atualizado ou recusado, está em `results`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPCustomerBatchResponse"
                },
                "example": {
                  "success": true,
                  "idempotent_replay": false,
                  "summary": {
                    "total": 3,
                    "created": 1,
                    "updated": 1,
                    "rejected": 1
                  },
                  "results": [
                    {
                      "index": 0,
                      "external_id": "cliente-0001",
                      "status": "CREATED",
                      "data": {
                        "external_id": "cliente-0001",
                        "type": "PF"
                      }
                    },
                    {
                      "index": 1,
                      "external_id": "cliente-0002",
                      "status": "UPDATED",
                      "data": {
                        "external_id": "cliente-0002",
                        "type": "PJ"
                      }
                    },
                    {
                      "index": 2,
                      "status": "REJECTED",
                      "error": {
                        "code": "invalid_customer",
                        "message": "Item recusado por validação."
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Idempotency-Key` ausente ou inválida (`invalid_idempotency_key`):\nvazia depois do trim, com mais de 255 bytes, com caractere de\ncontrole ou com o prefixo reservado `snx-derived:`. Corpo ilegível e\n`customers` fora de 1 a 1.000 itens não caem aqui (são 422\n`INVALID_BATCH`), e item inválido volta recusado em `results`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "409": {
            "description": "Idempotency-Key reusada com corpo diferente (KYT_IDEMPOTENCY_CONFLICT)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "413": {
            "description": "Corpo acima de 1 MiB. O escudo de borda recusa antes da\nautenticação, com `BODY_TOO_LARGE`; a própria operação repete o teto\ncom `BATCH_PAYLOAD_TOO_LARGE`. Nada foi gravado. Lote com mais de\n1.000 titulares dentro do teto de tamanho é 422 `INVALID_BATCH`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Lote recusado por inteiro, sem gravar nada: `INVALID_BATCH` (corpo\nilegível, campo desconhecido na raiz, ou `customers` fora de 1 a\n1.000 itens) ou `DUPLICATE_EXTERNAL_ID` (o lote repete um\n`external_id`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições atingido; nada foi processado. Valem as formas\nde `ErroLimiteDeRequisicoes` (limite da chave, limite global e escudo\nde borda) e os limites próprios do lote: por ator\n(`BATCH_RATE_LIMIT_ACTOR`) e por empresa (`BATCH_RATE_LIMIT_TENANT`),\nambos por hora e com `Retry-After: 3600` (`ErroLimiteDoLote`), e o\norçamento de itens da empresa (`BATCH_ITEM_BUDGET_EXCEEDED`,\n`ErrorResponseCoded`, também com `Retry-After: 3600`). Espere o\n`Retry-After` antes de repetir.\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-RateLimit-Limit": {
                "description": "Teto da janela do limitador que respondeu, quando ele informa.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requisições restantes na janela, quando o limitador informa.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroLimiteDoLote"
                    },
                    {
                      "$ref": "#/components/schemas/ErroLimite"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Matrícula em lote indisponível: chaves da empresa, proteção do lote,\nrecibo do lote ou banco (`ErroTexto`); nenhum item do lote ficou\ngravado. Sem contador de limite de lote, a rota recusa em vez de\ndeixar passar (`BATCH_RATE_LIMIT_UNAVAILABLE`). As camadas anteriores\nà operação também respondem 503, nada processado: limitador por IP ou\nda empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`), limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/wallets": {
      "post": {
        "operationId": "enrollVASPWallet",
        "summary": "Cadastra carteira de cliente final com screening fail-closed",
        "description": "Vincula uma carteira a um cliente já cadastrado. `mode=sync`\n(default) responde 201 com o resultado do screening; `mode=async`\nresponde 202 com status PENDING (fail-closed até o resultado).\nCarteira já conhecida da empresa responde 200 com o resultado\narmazenado. Suporta Idempotency-Key. Escopo exigido:\n`vasp:wallets:write`.\n\nO endereço é validado pela rede (checksum EIP-55, bech32, base58 e\nafins) e gravado só cifrado, com o HMAC da empresa para igualdade; o\nendereço em claro e o prefixo não são armazenados. A busca da carteira\nexistente cobre também as carteiras cadastradas antes da cifra, que são\nconvertidas na mesma linha, sem duplicar. Carteira de cliente nunca é\nanexada a carteira institucional do mesmo endereço (409\nwallet_is_institutional). `custody_type` é opcional; ausente fica\nregistrado como não informado.\n",
        "tags": [
          "VASP - Carteiras"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "sync",
                "async"
              ],
              "default": "sync"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VASPWalletEnrollRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Carteira já existente vinculada, ou replay idempotente",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "idempotent_replay": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Carteira nova cadastrada (mode=sync)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Carteira aceita para screening assíncrono (mode=async)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo JSON ilegível (texto em `error`), ou campos, `mode` ou `Idempotency-Key`\ninválidos (`invalid_wallet`, `invalid_mode`, `invalid_idempotency_key`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Cliente da carteira inexistente nesta empresa (texto em `error`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Endereço, external_id ou Idempotency-Key já existem\n(wallet_already_exists, KYT_IDEMPOTENCY_CONFLICT); o endereço é de\ncarteira institucional da empresa (wallet_is_institutional); ou o\nendereço casa mais de uma carteira antiga (wallet_duplicate_legacy,\ncom `error.wallet_ids`: desative as duplicatas por DELETE e reenvie).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPWalletErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro indisponível ou cifra do endereço indisponível; nenhuma\ncarteira foi gravada. As camadas anteriores à operação também respondem 503,\nnada processado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da\nconta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/wallets/{id}": {
      "get": {
        "operationId": "getVASPWalletEnrollment",
        "summary": "Consulta o enrollment e o status de screening de uma carteira",
        "description": "Escopo exigido: `vasp:read`.",
        "tags": [
          "VASP - Carteiras"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "200": {
            "description": "Enrollment da carteira",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`id` que não é UUID (`invalid_wallet_id`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Carteira inexistente nesta empresa (texto em `error`). Com chave válida,\n`route_not_found` diz que o caminho ou o método não corresponde a operação\nservida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de cadastro de carteiras indisponível. As camadas anteriores à operação\ntambém respondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deactivateVASPWallet",
        "summary": "Desativa a carteira (desativação lógica, o wallet_id continua válido)",
        "description": "Desativação LÓGICA: a linha fica, o `wallet_id` continua válido no GET\n(com `status: deactivated`), e alertas, casos, SAR e transações antigos\ncontinuam apontando para ela. A carteira sai do monitoramento contínuo\ne da coleta on-chain. Recusa com 409 `wallet_has_open_process`, com\n`error.case_ids` e `error.sar_ids`, quando a carteira está ligada a caso\naberto ou a SAR não enviado, pelos alertas e transações da carteira.\nA segunda chamada devolve 200 sem nova escrita. Emite o evento\n`wallet.deactivated`. Para voltar a monitorar, use PATCH com\n`{\"status\":\"active\"}`: o `wallet_id` é o mesmo. Escopo exigido:\n`vasp:wallets:write`.\n",
        "tags": [
          "VASP - Carteiras"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "200": {
            "description": "Carteira desativada (ou já estava)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`id` que não é UUID (`invalid_wallet_id`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Carteira inexistente nesta empresa (texto em `error`). Com chave válida,\n`route_not_found` diz que o caminho ou o método não corresponde a operação\nservida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Caso aberto ou SAR não enviado (wallet_has_open_process, com\n`error.case_ids` e `error.sar_ids`), ou carteira institucional, que\ntem rota própria de administração (wallet_is_institutional).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPWalletErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Ciclo de vida de carteira, verificação de processos ou trilha de auditoria\nindisponível; nada foi alterado. As camadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateVASPWallet",
        "summary": "Reativa a carteira, reenvia o endereço ou declara a custódia",
        "description": "Até três alterações na mesma chamada, gravando só o que muda:\n`status: active` reativa a carteira desativada mantendo o MESMO\n`wallet_id` e emite `wallet.linked` de novo; `address` com\n`blockchain` reenvia o endereço de carteira cadastrada antes da\ncifra, que passa a ser gravado cifrado (422 `address_mismatch` se o\nendereço não é o desta carteira); `custody_type` declara a custódia.\nA chamada repetida não grava nada, nem trilha nem evento. 409\n`wallet_address_active_elsewhere`, com `error.wallet_id`, quando\noutra carteira ativa da empresa passou a ter o mesmo endereço enquanto\nesta estava desativada. Escopo exigido: `vasp:wallets:write`.\n",
        "tags": [
          "VASP - Carteiras"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VASPWalletPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estado atual da carteira",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPWalletEnrollment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo JSON ilegível (texto em `error`), ou `id` e campos inválidos\n(`invalid_wallet_id`, `invalid_wallet`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Carteira inexistente nesta empresa (texto em `error`). Com chave válida,\n`route_not_found` diz que o caminho ou o método não corresponde a operação\nservida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Outra carteira ativa com o mesmo endereço\n(wallet_address_active_elsewhere, com `error.wallet_id`), endereço\nde outra carteira ativa (wallet_duplicate_legacy) ou carteira\ninstitucional (wallet_is_institutional).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPWalletErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "422": {
            "description": "O endereço enviado não é o desta carteira (address_mismatch)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Ciclo de vida de carteira, cifra ou trilha de auditoria indisponível; nada foi\nalterado. As camadas anteriores à operação também respondem 503, nada\nprocessado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/screenings": {
      "post": {
        "operationId": "screenTransaction",
        "summary": "Recomendação de risco para transação em tempo real (KYT)",
        "description": "Submete uma transação pré ou pós-broadcast e recebe a recomendação\nsíncrona `ALLOW`, `REVIEW`, `HOLD` ou `DENY`, com score de risco,\nsanções, regras disparadas e sinais internos de possível aplicabilidade.\n`DENY` somente com base em sanção (contraparte em lista de sanções ou\ncom categoria de sanção) ou por regra de bloqueio que a própria empresa\nconfigurou no painel. Os demais riscos (misturador, darknet, valor,\nvelocidade e outros) saem `REVIEW`, com `risk_level` preservado e o\nmotivo do rebaixamento em `reasons`. Exceção: quando a empresa escolhe,\nno painel, bloquear automaticamente a proibição legal do misturador\n(Res. BCB nº 520/2025, art. 90, III, e parágrafo único), a contraparte\nmisturador sai `DENY` com o motivo \"política do cliente: proibição\nlegal misturador\" em `reasons`. Sem essa escolha, que por padrão fica\ndesligada, ela segue `REVIEW` com o rótulo da proibição.\nA recomendação é análise auxiliar. A decisão operacional e o desfecho de\nsuspeição pertencem ao cliente e são registrados em `client_decision`.\nOs campos legados em `obligations` não confirmam obrigação, envio ou\nhomologação de Travel Rule, COAF ou DeCripto.\nEvidência necessária indisponível ou incompleta (contraparte não\ntriável, provedor indisponível, timeout ou resposta incompleta) conclui\na análise em `HOLD`: HTTP 200, `fail_closed: true` e o motivo em\n`reasons`, com a orientação de reprocessar com a mesma\nIdempotency-Key. `HOLD` é retenção temporária, não bloqueio, e nunca\nvira `ALLOW`; `DENY` por sanção ou regra do cliente prevalece sobre ele.\nSem resultado do motor, ou com falha de persistência, auditoria ou\ninfraestrutura, a rota responde 503 e nada é registrado nem cobrado.\nReplay idempotente via header Idempotency-Key.\n\n`customer_id` (opcional) identifica a matrícula do titular na empresa\nautenticado: é o `id` UUID devolvido por `POST /vasp/customers`, com a\nmesma semântica do `customer_id` de `POST /vasp/wallets`. Quando\nenviado, toda resposta de sucesso inclui `customer_id` e\n`holder_resolution`, mesmo com o cruzamento desligado. `RESOLVED`\ninforma que a matrícula pertence aa empresa; não afirma enriquecimento\nnem triagem negativa. `NOT_FOUND` cobre UUID desconhecido ou de outro\nempresa, sem alterar score, recomendação ou cobrança, e as regras que\ndependem do titular ficam fora da avaliação. Com o cruzamento\ndesligado, nenhuma fonte declarada ou de KYC é consultada; somente as\nfontes autorizadas pelo contrato podem contribuir para a análise. Os\nsinais cruzados seguem a regra de `DENY` desta operação: só sanção, ou\na política de bloqueio da própria empresa, bloqueia. Sem `customer_id`,\n`holder_resolution` e `customer_id` não vêm na resposta.\n\n`customer_id` inválido (vazio, nulo, UUID zero, fora do formato\ncanônico ou de outro tipo) responde 422 `KYT_CUSTOMER_ID_INVALID`, e a\nanálise não roda. Falha de contrato ou de fonte do titular responde 503\n`KYT_HOLDER_UNAVAILABLE` antes da análise, sem registro nem cobrança. A\nrepetição idempotente conserva a resolução registrada no snapshot\noriginal; snapshot gravado antes desta resolução, cuja requisição\ntrazia `customer_id`, responde 500 `KYT_SNAPSHOT_INVALID` e não\nreexecuta a análise. Dado não informado nunca vira \"não\".\n`customer_ref` não muda: continua sendo a referência opaca do seu lado.\n\nCom `customer_ref` informado, quando a leitura da identidade (chave de\npseudonimização da referência) está indisponível, a resposta é 503\n`KYT_IDENTITY_UNAVAILABLE`, com `data.recommendation` `HOLD` e\n`data.fail_closed` `true`: nenhuma análise é criada, nenhuma regra é\navaliada e nada é cobrado. A decisão continua pertencendo ao cliente.\n\nA sanção é medida na data da operação. Operação posterior ou igual à\npublicação vigente da lista é decidida pela versão publicada,\ninformada em `sanctions.coverage`. Operação anterior à publicação\ndepende do histórico oficial de designações e, sem ele, retorna\n`HOLD` com `fail_closed` verdadeiro. Fonte obrigatória vencida, não\npublicada ou não atestada também retorna `HOLD`, nunca `ALLOW`.\n\nNesta versão, exposição on-chain (exposures, graph e typologies) e\nblocklist de rede sem base de golpes carregada não são analisadas. A\nbase atual cobre ETH e as redes EVM que a compartilham (BASE, ARB, BSC,\nMATIC, AVAX, OP e FTM). Essas dimensões saem\nem `not_covered`, no topo da resposta, nunca dentro de `coverage`.\n`ALLOW` com entradas em `not_covered` significa que elas não foram\nanalisadas. Triagens gravadas antes desta versão não trazem o campo.\n",
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Mesma chave + corpo igual = replay; corpo diferente = 409. Opcional: quando ausente e `client_transaction_id` presente, o servidor DERIVA a chave de (rota, corpo, janela), e a repetição idêntica vira replay em vez de segundo screening cobrado. O alcance da derivação é de no mínimo 10 e no máximo 20 minutos, porque a consulta prova o bucket corrente e o anterior. Sem `client_transaction_id` não há deduplicação possível e duas chamadas iguais continuam sendo duas operações. O prefixo `snx-derived:` é RESERVADO ao servidor em TODAS as rotas desta API: uma `Idempotency-Key` que comece com ele é recusada com 400."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "asset"
                ],
                "properties": {
                  "client_transaction_id": {
                    "type": "string"
                  },
                  "customer_ref": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "Referência opaca do cliente. Ocorrência inequívoca de CPF, CNPJ, e-mail ou telefone é substituída por hash determinístico antes de persistir, alertar ou notificar."
                  },
                  "customer_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": false,
                    "description": "Opcional. UUID não nulo do titular matriculado: o `id` devolvido\npor `POST /vasp/customers`, com a mesma semântica do\n`customer_id` de `POST /vasp/wallets`. Omitir quando não houver\ntitular. Valor vazio, `null`, UUID zero, fora do formato\ncanônico de 36 caracteres ou de outro tipo responde 422\n`KYT_CUSTOMER_ID_INVALID`. UUID bem formado que não identifica\ntitular desta empresa não muda a decisão: a resposta traz\n`holder_resolution` `NOT_FOUND`. `customer_ref` continua como\nestá.\n",
                    "example": "00000000-0000-4000-8000-000000000001"
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "DEPOSIT",
                      "WITHDRAWAL",
                      "INTERNAL"
                    ]
                  },
                  "asset": {
                    "type": "string",
                    "example": "USDT"
                  },
                  "chain": {
                    "type": "string",
                    "example": "tron",
                    "description": "Rede on-chain. OPCIONAL nesta rota, e continua opcional: o screening não grava a transação em histórico consultado por rede, então a ausência da chain não torna nada ilegível depois. Ela é, porém, necessária para triar a contraparte: sem `chain`, nem sanções nem blocklist consultam `counterparty_address`, e a decisão cai no fail-closed desta rota, voltando como `HOLD` com `fail_closed: true` e a razão declarada em `reasons`. Para consultar a contraparte nos dois caminhos, envie `chain` sempre que enviar `counterparty_address`. A rota irmã POST /transactions/analyze, que GRAVA a transação, é mais estrita: lá `blockchain` é obrigatória junto de qualquer endereço on-chain e a ausência devolve 400."
                  },
                  "counterparty_address": {
                    "type": "string"
                  },
                  "tx_hash": {
                    "type": "string",
                    "description": "vazio = intenção pré-broadcast"
                  },
                  "operation_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Opcional. Data e hora da operação, em RFC 3339. É o instante em\nque a sanção da contraparte é medida contra o intervalo de\ndesignação da lista. A ausência NUNCA responde 400, nem com\n`tx_hash` preenchido: sem este campo o servidor usa a data do\nbloco da transação, quando já a mediu, e senão o instante em\nque recebeu a requisição. A origem da data usada volta em\n`operation_time_source`. Valor que não é data RFC 3339\nresponde 400 `KYT_REQUEST_INVALID`. Esta data não atesta\nobrigação legal e não altera a decisão final, que\npermanece do cliente.\n"
                  },
                  "amount": {
                    "type": "number",
                    "format": "double",
                    "nullable": true,
                    "minimum": 0,
                    "description": "Quantidade do ativo indicado em `asset`, na unidade decimal nativa desse ativo. A omissão (ou `null`) impede a valoração no servidor e resulta em `HOLD` temporário. Para `asset` BRL o valor em reais é o próprio `amount`, inclusive zero informado. Valor negativo responde 400 `KYT_AMOUNT_INVALID`.",
                    "example": 1500.25
                  },
                  "fiat_value_brl": {
                    "type": "number",
                    "format": "double",
                    "nullable": true,
                    "description": "Valor em reais declarado pelo integrador, não em centavos. É apenas preservado e devolvido em `declared_fiat_value_brl`: não substitui a cotação do servidor e não tem efeito decisório. Valor não finito responde 400 `KYT_FIAT_VALUE_INVALID`.",
                    "example": 7500.5
                  },
                  "counterparty_vasp": {
                    "type": "string"
                  },
                  "travel_rule": {
                    "nullable": true,
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "UNKNOWN",
                          "REQUIRED",
                          "NOT_REQUIRED"
                        ]
                      },
                      {
                        "type": "boolean"
                      }
                    ],
                    "description": "Estado informado de Travel Rule. Os booleanos `true` e `false` correspondem a `REQUIRED` e `NOT_REQUIRED`. Omissão ou `null` corresponde a `UNKNOWN`, sem `HOLD` por essa ausência. Outro valor responde 400 `KYT_REQUEST_INVALID`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recomendação de screening e estado da decisão do cliente",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "idempotent_replay": {
                      "type": "boolean"
                    },
                    "data": {
                      "$ref": "#/components/schemas/VASPScreeningDecision"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo, campo ou `Idempotency-Key` inválida (`KYT_REQUEST_INVALID`,\n`KYT_ASSET_REQUIRED`, `KYT_AMOUNT_INVALID`, `KYT_IDEMPOTENCY_INVALID` e os\ndemais `KYT_*_INVALID`), ou `operation_at` mais de 5 minutos adiante do\nrelógio do servidor (`KYT_OPERATION_AT_IN_FUTURE`), recusado antes de\nqualquer análise, persistência ou cobrança. Até 5 minutos é aceito como\nfolga de relógio; a tolerância é parâmetro operacional, não prazo de norma.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                },
                "examples": {
                  "operation_at_no_futuro": {
                    "summary": "operation_at além da tolerância de 5 minutos",
                    "value": {
                      "success": false,
                      "error": {
                        "code": "KYT_OPERATION_AT_IN_FUTURE",
                        "http_status": 400,
                        "message": "operation_at está no futuro além da tolerância de 5 minutos.",
                        "action": "Envie a data real da operação (RFC3339) ou omita o campo.",
                        "retriable": false
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegadoModuloKYT"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "409": {
            "description": "`Idempotency-Key` reusada com corpo diferente (`KYT_IDEMPOTENCY_CONFLICT`) ou\nanálise idêntica em processamento (`KYT_IDEMPOTENCY_IN_PROGRESS`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "422": {
            "description": "Transação que o motor não consegue analisar com os dados enviados\n(`KYT_INPUT_UNSCREENABLE`), ou `customer_id` inválido\n(`KYT_CUSTOMER_ID_INVALID`: vazio, nulo, UUID zero, fora do formato\ncanônico ou de outro tipo); nos dois casos a análise não roda. No sandbox do desenvolvedor\n(`sandbox.api.crypto.sentinexrisk.com`), também endereço de contraparte\nque não é uma das fixtures do catálogo `sandbox-fixtures-v1`\n(`SANDBOX_FIXTURE_NOT_SUPPORTED`, no mesmo envelope `KYTPublicErrorResponse`,\ncom `action` apontando para `/api/v1/sandbox/fixtures`): nenhuma consulta\nde DNS ou de provider é feita nem cobrança. A produção nunca emite este\ncódigo.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "description": "Falha inesperada (`ErroTexto`) ou análise gravada que não pôde ser lida na\nrepetição (`KYT_SNAPSHOT_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Evidência essencial, persistência, auditoria, medição ou motor indisponível\n(`KYT_*_UNAVAILABLE`, incluindo `KYT_HOLDER_UNAVAILABLE` quando a\nresolução ou a evidência do titular falha), ou leitura do módulo\ncontratado indisponível (`MODULE_STORE_UNAVAILABLE`). A identidade\nindisponível (`KYT_IDENTITY_UNAVAILABLE`) usa o envelope público de\nerro acrescido de `data.recommendation` `HOLD` e `data.fail_closed`\n`true`, sem criar análise, avaliar regras ou registrar consumo. As\ncamadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/KYTIdentityUnavailableResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/screenings/{id}": {
      "get": {
        "operationId": "getScreening",
        "summary": "Busca uma recomendação de screening anterior",
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "200": {
            "description": "Recomendação de screening e decisão registrada pelo cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPScreeningGetResponse"
                }
              }
            }
          },
          "400": {
            "description": "`id` que não é UUID (`KYT_SCREENING_ID_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Análise inexistente nesta empresa (`KYT_SCREENING_NOT_FOUND`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "description": "Falha inesperada (`ErroTexto`) ou análise gravada que não pôde ser lida\n(`KYT_SNAPSHOT_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Análise indisponível para consulta (`KYT_SERVICE_UNAVAILABLE` ou\n`KYT_PERSISTENCE_UNAVAILABLE`). As camadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/screenings/{id}/decision": {
      "patch": {
        "operationId": "submitScreeningDecision",
        "summary": "Registra a decisão do cliente pelo alias legado",
        "description": "Alias de compatibilidade para `/vasp/screenings/{id}/client-decision`.\nAceita temporariamente `resolution` e `reason`, conforme o mapa publicado\nna documentação de API. A resposta usa somente `client_decision`.\n",
        "deprecated": true,
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resolution"
                ],
                "properties": {
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "CLEARED",
                      "CONFIRMED_FRAUD",
                      "FALSE_POSITIVE",
                      "ESCALATED_COAF"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "description": "Justificativa textual opcional."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Desfecho registrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTClientDecisionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Identificador, corpo ou combinação de decisão inválida\n(`KYT_SCREENING_ID_INVALID`, `KYT_REQUEST_INVALID`,\n`KYT_CLIENT_DECISION_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegadoModuloKYT"
          },
          "404": {
            "description": "Análise inexistente nesta empresa (`KYT_SCREENING_NOT_FOUND`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Decisão do cliente já registrada para esta análise\n(`KYT_CLIENT_DECISION_ALREADY_RECORDED`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Persistência, auditoria ou transação da requisição indisponível\n(`KYT_PERSISTENCE_UNAVAILABLE`, `KYT_AUDIT_UNAVAILABLE`, ou texto em `error`\ncom `Retry-After: 1` quando a transação conflitou), ou leitura do módulo\ncontratado indisponível (`MODULE_STORE_UNAVAILABLE`). As camadas anteriores à\noperação também respondem 503, nada processado: limitador por IP ou da empresa sem\ncontagem (`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e\nlimitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/screenings/{id}/client-decision": {
      "patch": {
        "operationId": "submitScreeningClientDecision",
        "summary": "Registra a decisão operacional pertencente ao cliente",
        "description": "Registra uma única vez a ação operacional e o desfecho de suspeição\natribuídos ao cliente. A recomendação da Sentinex não é alterada.\n`operational_action` e `suspicion_outcome` são dimensões independentes.\nA projeção para o vocabulário legado é documentada no contrato KYT v1.\n",
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KYTClientDecisionRequest"
              },
              "example": {
                "client_decision": {
                  "operational_action": "HOLD",
                  "suspicion_outcome": "SUSPICIOUS",
                  "rationale": "Evidência encaminhada para análise reforçada do cliente."
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decisão do cliente registrada de forma imutável",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTClientDecisionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Identificador, corpo ou combinação de decisão inválida\n(`KYT_SCREENING_ID_INVALID`, `KYT_REQUEST_INVALID`,\n`KYT_CLIENT_DECISION_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegadoModuloKYT"
          },
          "404": {
            "description": "Análise inexistente nesta empresa (`KYT_SCREENING_NOT_FOUND`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Decisão do cliente já registrada para esta análise\n(`KYT_CLIENT_DECISION_ALREADY_RECORDED`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Persistência, auditoria ou transação da requisição indisponível\n(`KYT_PERSISTENCE_UNAVAILABLE`, `KYT_AUDIT_UNAVAILABLE`, ou texto em `error`\ncom `Retry-After: 1` quando a transação conflitou), ou leitura do módulo\ncontratado indisponível (`MODULE_STORE_UNAVAILABLE`). As camadas anteriores à\noperação também respondem 503, nada processado: limitador por IP ou da empresa sem\ncontagem (`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e\nlimitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/screenings/{id}/tx-hash": {
      "patch": {
        "operationId": "attachScreeningTxHash",
        "summary": "Anexa o hash da transação transmitida ao screening (pré-checagem em dois passos)",
        "description": "Depois de pré-checar um saque (screening sem `tx_hash`) e transmitir a\ntransação, o cliente anexa o hash ao mesmo screening. O registro liga a\nrecomendação e a decisão do cliente ao fato que elas autorizaram ou\nrecusaram. Não reavalia o screening, não altera a recomendação nem a\ndecisão do cliente, não exige decisão prévia e não gera cobrança. Vale\ntambém para recomendação `HOLD` ou `DENY`: a recomendação não é ordem.\n\nRegistro imutável e idempotente: o primeiro hash válido vence. Repetir o\nmesmo hash, em qualquer grafia canônica equivalente, devolve 200 com\n`idempotent_replay: true` e não escreve de novo; outro hash sobre um\nscreening que já tem hash (do `POST` ou de anexo anterior) devolve 409.\nO mesmo hash pode constar em screenings diferentes do mesma empresa.\n\nO hash é declarado pelo cliente: o formato é validado conforme a rede do\nscreening, mas a existência da transação na rede não é medida\n(`on_chain_verification` é sempre `NOT_MEASURED`). Mesmo escopo\n(`vasp:transactions:screen`) e mesmo portão do módulo KYT das demais\nescritas de screening; o anexo não gera evento de saída.\n",
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KYTTxHashAttachRequest"
              },
              "example": {
                "tx_hash": "<hash de 32 bytes em hex da transação transmitida>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hash anexado, ou repetição idêntica (`idempotent_replay`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTTxHashAttachResponse"
                }
              }
            }
          },
          "400": {
            "description": "Identificador, corpo ou hash inválido (`KYT_SCREENING_ID_INVALID`,\n`KYT_REQUEST_INVALID`, `KYT_TX_HASH_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegadoModuloKYT"
          },
          "404": {
            "description": "Análise inexistente nesta empresa (`KYT_SCREENING_NOT_FOUND`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "O screening já tem outro hash de transação\n(`KYT_TX_HASH_ALREADY_ATTACHED`); o registro é imutável.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Persistência, auditoria ou transação da requisição indisponível\n(`KYT_PERSISTENCE_UNAVAILABLE`, `KYT_AUDIT_UNAVAILABLE`), ou leitura do\nmódulo contratado indisponível (`MODULE_STORE_UNAVAILABLE`); a transação\ninteira é desfeita e o hash não fica gravado sem a trilha. As camadas\nanteriores à operação também respondem 503, nada processado: limitador\npor IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`), limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/kyt-report": {
      "post": {
        "operationId": "generateVASPKYTReport",
        "summary": "Gera laudo KYT por API key",
        "description": "Gera o laudo sobre uma carteira da própria empresa. Requer o escopo\n`vasp:transactions:screen` e o módulo KYT. `format` aceita `pdf`, `html`\nou `json`. O JSON usa `recommendation`; a decisão operacional continua\npendente até o cliente registrá-la.\n\nO par endereço/rede é validado antes de qualquer consulta externa: par\ninválido responde 400 `KYT_WALLET_INVALID` com `reason`\n`address_chain_mismatch` e nenhuma chamada externa é feita. `report.id` é o\nidentificador único da emissão e `case.case_id` o agrupador estável da\ncarteira, na forma canônica (veja `KYTReportJSON` para a compatibilidade\ncom laudos anteriores). Valor em reais sem cotação completa não sai no\nJSON (`scope.brl_status` `nao_cotado`), e o monitoramento só é afirmado\nquando uma fonte o comprova (`monitoring`).\n",
        "tags": [
          "VASP - Screening"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "application/vnd.sentinex.kyt.v2+json seleciona a projeção documental canônica; application/json preserva v1.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pdf",
                "html",
                "json"
              ],
              "default": "pdf"
            }
          },
          {
            "name": "profile",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "operational",
                "complete",
                "regulatory"
              ],
              "default": "operational"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KYTReportRequest"
              },
              "example": {
                "profile": "operational",
                "wallet": {
                  "address": "0x1111111111111111111111111111111111111111",
                  "blockchain": "ethereum"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Laudo gerado e emissão registrada na auditoria",
            "headers": {
              "Content-Disposition": {
                "description": "PDF canônico, quando o snapshot está presente: `KYT-OPERACIONAL-<report-id>.pdf`,\n`KYT-COMPLETO-<report-id>.pdf` ou `KYT-REGULATORIO-<report-id>.pdf`. Sem snapshot\ncanônico permanece o formato legado `kyt-{perfil}-{emitido-em-UTC}-{report-id}.{formato}`\n(por exemplo, `kyt-operational-20261002T120000Z-<report-id>.pdf`).\n`attachment` no PDF; `inline` no HTML e no JSON. Exposto ao navegador\npelo CORS.\n",
                "schema": {
                  "type": "string"
                }
              },
              "X-Report-Format": {
                "description": "`html` ou `json` quando o artefato é HTML ou JSON; ausente no PDF. Exposto\nao navegador pelo CORS.\n",
                "schema": {
                  "type": "string",
                  "enum": [
                    "html",
                    "json"
                  ]
                }
              },
              "X-Report-Audit-Trail": {
                "description": "`recorded` quando a emissão foi gravada no histórico imutável da empresa,\n`not-recorded` quando não foi.\n",
                "schema": {
                  "type": "string",
                  "enum": [
                    "recorded",
                    "not-recorded"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTReportJSON"
                }
              },
              "application/vnd.sentinex.kyt.v2+json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTReportDocumentV2"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Carteira, rede, perfil ou formato inválido (`KYT_REQUEST_INVALID`,\n`KYT_WALLET_REQUIRED`, `KYT_WALLET_INVALID`, `KYT_REPORT_PROFILE_INVALID`,\n`KYT_REPORT_FORMAT_INVALID`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegadoModuloKYT"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "502": {
            "description": "Evidência on-chain ou renderização do laudo falhou no provedor\n(`KYT_EVIDENCE_UNAVAILABLE`, `KYT_REPORT_RENDER_FAILED`,\n`KYT_REPORT_SERIALIZATION_FAILED`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KYTPublicErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Dependência essencial, auditoria, renderização ou transação da requisição\nindisponível (`KYT_SERVICE_UNAVAILABLE`, `KYT_EVIDENCE_UNAVAILABLE`,\n`KYT_AUDIT_UNAVAILABLE`, `KYT_REPORT_RENDER_UNAVAILABLE`, ou texto em\n`error`), ou leitura do módulo contratado indisponível\n(`MODULE_STORE_UNAVAILABLE`). As camadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTextoComCodigo"
                    },
                    {
                      "$ref": "#/components/schemas/KYTPublicErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/sars": {
      "get": {
        "operationId": "listVASPPendingSARs",
        "summary": "Lista SARs pendentes da empresa VASP",
        "description": "Retorna apenas a projeção enxuta dos SARs pendentes da própria empresa\nautenticado por API-key. A lista não expõe narrative, dados pessoais do\ncliente final nem UUIDs internos de preparador, revisor ou aprovador.\n",
        "tags": [
          "VASP - Compliance"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "SARs pendentes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "sar_number": {
                            "type": "string"
                          },
                          "report_type": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "DRAFT",
                              "PENDING_REVIEW",
                              "PENDING_APPROVAL",
                              "SUBMITTED",
                              "ACKNOWLEDGED",
                              "REJECTED"
                            ]
                          },
                          "filing_deadline": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "filed_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API regulatória indisponível (`regulatory_unavailable`). As camadas anteriores\nà operação também respondem 503, nada processado: limitador por IP ou da empresa sem\ncontagem (`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e\nlimitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/sars/{id}": {
      "get": {
        "operationId": "getVASPSAR",
        "summary": "Busca detalhe de um SAR da empresa VASP",
        "description": "Retorna o detalhe read-only de um SAR no escopo da empresa. Não expõe campos\ninternos de preparador, revisor ou aprovador e não permite submissão ao\nSISCOAF.\n",
        "tags": [
          "VASP - Compliance"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "200": {
            "description": "SAR da empresa",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "sar_number": {
                          "type": "string"
                        },
                        "report_type": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "DRAFT",
                            "PENDING_REVIEW",
                            "PENDING_APPROVAL",
                            "SUBMITTED",
                            "ACKNOWLEDGED",
                            "REJECTED"
                          ]
                        },
                        "filing_deadline": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "filed_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "risk_indicators": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "narrative": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`id` de SAR que não é UUID.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "SAR inexistente nesta empresa (texto em `error`). Com chave válida,\n`route_not_found` diz que o caminho ou o método não corresponde a operação\nservida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API regulatória indisponível (`regulatory_unavailable`). As camadas anteriores\nà operação também respondem 503, nada processado: limitador por IP ou da empresa sem\ncontagem (`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e\nlimitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/decripto/monthly": {
      "get": {
        "operationId": "generateVASPDeCriptoMonthly",
        "summary": "Prepara artefato DeCripto mensal interno",
        "description": "Layout e transporte externos não homologados por default.",
        "tags": [
          "VASP - Compliance"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 2020,
              "maximum": 2100,
              "example": 2026
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12,
              "example": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artefato mensal interno preparado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObjectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro `year` ou `month` inválido.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API regulatória indisponível (`regulatory_unavailable`) ou cotação PTAX\nindisponível para converter volumes (texto em `error`); nenhum relatório\ngerado. As camadas anteriores à operação também respondem 503, nada\nprocessado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/decripto/annual": {
      "get": {
        "operationId": "generateVASPDeCriptoAnnual",
        "summary": "Prepara artefato DeCripto anual interno",
        "description": "Layout e transporte externos não homologados por default.",
        "tags": [
          "VASP - Compliance"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 2020,
              "maximum": 2100,
              "example": 2026
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artefato anual interno preparado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObjectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Parâmetro `year` inválido.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API regulatória indisponível (`regulatory_unavailable`) ou cotação PTAX\nindisponível para converter volumes (texto em `error`); nenhum relatório\ngerado. As camadas anteriores à operação também respondem 503, nada\nprocessado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/alerts": {
      "get": {
        "operationId": "listClientAlerts",
        "summary": "Lista alertas da empresa (poll server-to-server)",
        "description": "Complementa o webhook outbound. Retorna alertas no escopo da empresa com\nprojeção LGPD-safe (sem identidades internas de operador).\n\nA resposta traz a lista de alertas em `data` e a paginação no bloco\n`pagination`, com `total`, `limit` e `offset`; não há bloco `meta`.\n`limit` fora de 1 a 200 é ajustado para o extremo mais próximo e\n`offset` negativo vale 0, sem erro.\n",
        "tags": [
          "VASP - Alertas"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de alertas em `data` e paginação em `pagination`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VASPAlertListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "API de alertas indisponível. As camadas anteriores à operação também respondem\n503, nada processado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado\nda conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/webhooks": {
      "post": {
        "operationId": "createVASPWebhookSubscription",
        "summary": "Cadastra subscription de webhook outbound",
        "description": "Permite que a empresa VASP, autenticado por API-key, cadastre uma URL\nHTTPS pública para receber eventos outbound da Sentinex Risk Crypto.\nO cliente gera o secret HMAC, envia no corpo e o servidor nunca o ecoa.\n\nCada entrega traz no header `X-Sentinex-Event-Type` um dos eventos\nassinados, ou `webhook.test` quando disparada por\n`POST /vasp/webhooks/{id}/test`. `webhook.test` não pode ser assinado\nem `events`.\n\nSituação de cada evento aceito em `events`, numa integração de KYT:\n\n| Evento | Situação |\n|---|---|\n| `customer.created` | emitido |\n| `customer.updated` | aceito na assinatura, ainda não emitido |\n| `customer.deleted` | aceito na assinatura, ainda não emitido |\n| `wallet.linked` | emitido |\n| `wallet.deactivated` | emitido |\n| `kyc.completed` | aceito na assinatura, ainda não emitido |\n| `kyc.provider_status_changed` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.provider_decision_conflict` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.provider_evidence_refreshed` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.assessment_finalized` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.decision_proposal_created` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.decision_approval_recorded` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.decision_finalized` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.rekyc_scheduled` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.rekyc_started` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.rekyc_failed` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.partner_admitted` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.partner_relationship_ended` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.actor_invitation.expired` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyc.actor_invitation.reminder_rotated` | aceito na assinatura; emitido só pelo produto de KYC, não numa integração de KYT |\n| `kyt.screening.completed` | emitido |\n| `kyt.screening.sanction_hit` | emitido |\n| `kyt.case.client_decision.recorded` | emitido |\n| `alert.created` | emitido |\n| `alert.escalated` | emitido |\n| `alert.resolved` | emitido |\n",
        "tags": [
          "VASP - Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "target_url",
                  "secret"
                ],
                "properties": {
                  "target_url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https://",
                    "example": "https://vasp.example/webhooks/sentinex",
                    "description": "URL HTTPS pública que receberá os eventos."
                  },
                  "events": {
                    "type": "array",
                    "description": "Lista de eventos. Array vazio assina todos. A situação de cada evento (emitido, ou aceito na assinatura e ainda não emitido numa integração de KYT) está na tabela da descrição da operação.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "customer.created",
                        "customer.updated",
                        "customer.deleted",
                        "wallet.linked",
                        "wallet.deactivated",
                        "kyc.completed",
                        "kyc.provider_status_changed",
                        "kyc.provider_decision_conflict",
                        "kyc.provider_evidence_refreshed",
                        "kyc.assessment_finalized",
                        "kyc.decision_proposal_created",
                        "kyc.decision_approval_recorded",
                        "kyc.decision_finalized",
                        "kyc.rekyc_scheduled",
                        "kyc.rekyc_started",
                        "kyc.rekyc_failed",
                        "kyc.partner_admitted",
                        "kyc.partner_relationship_ended",
                        "kyc.actor_invitation.expired",
                        "kyc.actor_invitation.reminder_rotated",
                        "kyt.screening.completed",
                        "kyt.screening.sanction_hit",
                        "kyt.case.client_decision.recorded",
                        "alert.created",
                        "alert.escalated",
                        "alert.resolved"
                      ]
                    }
                  },
                  "description": {
                    "type": "string",
                    "description": "Descrição operacional da subscription."
                  },
                  "secret": {
                    "type": "string",
                    "minLength": 32,
                    "maxLength": 128,
                    "writeOnly": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription criada sem ecoar o secret HMAC",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "target_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Corpo ilegível, `target_url` ausente ou não HTTPS, `secret` fora de 32 a 128\nbytes ou evento inválido.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "413": {
            "$ref": "#/components/responses/ErroCorpoAcimaDoLimite"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Webhooks de saída indisponíveis ou cofre do segredo indisponível. As camadas\nanteriores à operação também respondem 503, nada processado: limitador por IP ou da empresa sem contagem (`ErroTexto`), estado da conta\n(`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave\n(`rate_limit_unavailable`) e verificação da chave\n(`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listVASPWebhookSubscriptions",
        "summary": "Lista subscriptions de webhook outbound da empresa",
        "tags": [
          "VASP - Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Lista no escopo da empresa de subscriptions ativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericObjectResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "$ref": "#/components/responses/ErroRotaNaoEncontrada"
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Webhooks de saída indisponíveis. As camadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/webhooks/{id}/test": {
      "post": {
        "operationId": "testVASPWebhookSubscription",
        "summary": "Enfileira um evento de teste para a subscription",
        "description": "Enfileira `webhook.test` pelo pipeline real de assinatura HMAC, retry,\nproteção SSRF e DLQ. A subscription é resolvida dentro da empresa da\nAPI key. No sandbox, somente este tipo de evento pode atravessar o gate\nde efeitos externos; eventos operacionais e regulatórios continuam\nbloqueados.\n",
        "tags": [
          "VASP - Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "202": {
            "description": "Evento de teste aceito para entrega assíncrona",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "subscription_id",
                    "event_id",
                    "status"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "subscription_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "event_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`id` que não é UUID.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Assinatura ativa inexistente nesta empresa (texto em `error`). Com chave\nválida, `route_not_found` diz que o caminho ou o método não corresponde a\noperação servida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Fila de entrega ou auditoria indisponível. As camadas anteriores à operação\ntambém respondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/vasp/webhooks/{id}": {
      "delete": {
        "operationId": "deleteVASPWebhookSubscription",
        "summary": "Remove uma subscription de webhook outbound",
        "description": "Remove logicamente uma subscription ativa da própria empresa.",
        "tags": [
          "VASP - Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ResourceID"
          }
        ],
        "responses": {
          "204": {
            "description": "Subscription removida"
          },
          "400": {
            "description": "`id` que não é UUID.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ErroChaveNaoAutenticada"
          },
          "402": {
            "$ref": "#/components/responses/TenantAccessPaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/ErroAcessoNegado"
          },
          "404": {
            "description": "Assinatura inexistente nesta empresa (texto em `error`). Com chave válida,\n`route_not_found` diz que o caminho ou o método não corresponde a operação\nservida (`ErrorResponseCoded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    },
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ErroLimiteDeRequisicoes"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "description": "Webhooks de saída indisponíveis. As camadas anteriores à operação também\nrespondem 503, nada processado: limitador por IP ou da empresa sem contagem\n(`ErroTexto`), estado da conta (`empresa_ACCESS_STATE_UNAVAILABLE`) e limitador da chave (`rate_limit_unavailable`) e verificação da chave (`api_key_verification_unavailable`).\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErroTexto"
                    },
                    {
                      "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponseCoded"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Chave de API por empresa para integração máquina-a-máquina (superfície\nVASP, `/api/v1/vasp/*`). Chaves emitidas a partir da 2.16.0 têm o formato\n`snxc_<env>_<identificador>_<segredo>` (identificador de 32 caracteres e\nsegredo de 64 hexadecimais, 107 caracteres no total); chaves anteriores,\n`snxc_<env>_<hex>`, continuam válidas até a expiração ou a revogação. Trate a\nchave como texto opaco. Envie no header `X-API-Key: snxc_live_...`. Se a\nplataforma não consegue verificar a chave, a resposta é 503\n`api_key_verification_unavailable`, e a requisição pode ser repetida com\nespera; nunca é tratada como chave válida. O backend também aceita\n`Authorization: Bearer snxc_...` como fallback, mas `X-API-Key` é o\nmodo canônico. Escopos least-privilege por rota (ex.:\n`vasp:transactions:screen`, `vasp:webhooks:manage`, `vasp:read`,\n`kyc:read` e `kyc:write`).\nAmbiente live e sandbox são isolados (uma chave sandbox não acessa\nlive e vice-versa). Rate limit por key/minuto com headers\n`X-RateLimit-Limit` / `X-RateLimit-Remaining` e `Retry-After` no 429.\nToda requisição por chave passa, logo depois da autenticação, pelo portão\nde acesso da empresa: só segue conta operacional com o\nacesso por API liberado pelo super admin. Recusas: 402 `TRIAL_EXPIRED`,\n403 `empresa_SUSPENDED`, `empresa_NOT_OPERATIONAL`, `API_ACCESS_DISABLED` ou\n`empresa_ACCESS_DENIED`, e 503 `empresa_ACCESS_STATE_UNAVAILABLE`. Desligar a\nAPI não revoga a chave: vale na requisição seguinte.\n"
      }
    },
    "parameters": {
      "ResourceID": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Mesma chave + corpo idêntico = replay idempotente (200 com\nidempotent_replay=true); mesma chave + corpo diferente = 409.\n"
      }
    },
    "responses": {
      "TenantAccessPaymentRequired": {
        "description": "Conta sem direito comercial (`TRIAL_EXPIRED`): trial vencido, trial sem\ndata ou empresa sem contrato. A requisição NÃO foi processada; uma\ntriagem recusada assim é transação não triada, nunca aprovada.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TenantAccessRefusalResponse"
            }
          }
        }
      },
      "ErroChaveNaoAutenticada": {
        "description": "A chave de API não autenticou a requisição, e nenhuma camada adiante\nrodou. `error.code` é `missing_api_key` (nenhuma chave em `X-API-Key`\nnem em `Authorization: Bearer snxc_...`) ou `invalid_api_key` (chave\ndesconhecida, revogada ou expirada). Quando a plataforma não consegue\nverificar a chave, a resposta é `503` com\n`api_key_verification_unavailable`, e não `401`: repita com backoff.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponseCoded"
            }
          }
        }
      },
      "ErroAcessoNegado": {
        "description": "Acesso recusado antes da operação, nesta ordem de camadas: o escudo de\nborda recusa travessia de diretório na URL (`ErroTexto`); a chave é\nrecusada pelo IP de origem (`api_key_ip_not_allowed`); o ambiente da\nchave não é aceito por esta implantação (`api_key_environment_required`\nou `api_key_environment_not_allowed`); a conta não pode usar a API\n(`TenantAccessRefusalResponse`: `empresa_SUSPENDED`,\n`empresa_NOT_OPERATIONAL`, `API_ACCESS_DISABLED` ou\n`empresa_ACCESS_DENIED`); a chave não tem o escopo da rota\n(`insufficient_scope`, com o escopo na mensagem). Não repita sem\ncorrigir a causa.\n",
        "content": {
          "application/json": {
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                },
                {
                  "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                },
                {
                  "$ref": "#/components/schemas/ErroTexto"
                }
              ]
            }
          }
        }
      },
      "ErroAcessoNegadoModuloKYT": {
        "description": "Acesso recusado antes da operação. Além das recusas de\n`ErroAcessoNegado` (escudo de borda, IP da chave, ambiente, conta e\nescopo), a empresa pode não ter o módulo de KYT contratado\n(`ModuleNotEnabledResponse`, `MODULE_NOT_ENABLED`, com\n`details.module`). Não repita sem corrigir a causa.\n",
        "content": {
          "application/json": {
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                },
                {
                  "$ref": "#/components/schemas/TenantAccessRefusalResponse"
                },
                {
                  "$ref": "#/components/schemas/ErroTexto"
                },
                {
                  "$ref": "#/components/schemas/ModuleNotEnabledResponse"
                }
              ]
            }
          }
        }
      },
      "ErroRotaNaoEncontrada": {
        "description": "Com chave válida, o caminho ou o método não corresponde a operação\nservida por esta implantação (`error.code` `route_not_found`).\nConfira método, caminho e o prefixo `/api/v1`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponseCoded"
            }
          }
        }
      },
      "ErroCorpoAcimaDoLimite": {
        "description": "Corpo acima de 1 MiB, recusado pelo escudo de borda antes da\nautenticação (`code` `BODY_TOO_LARGE`). Nada foi processado.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErroTextoComCodigo"
            }
          }
        }
      },
      "ErroLimiteDeRequisicoes": {
        "description": "Limite de requisições atingido; nada foi processado. Formas, pela camada\nque recusou: limite da chave (`ErrorResponseCoded`,\n`api_key_rate_limit_exceeded`, 60 por minuto quando a chave não define\noutro valor, `Retry-After: 60`); limite global por hora (`ErroLimite`,\n`retry_after` 3600 e `Retry-After: 3600`); escudo de borda por IP\n(`ErroLimite`: `RATE_LIMIT_HARD` acima de 1.000 por minuto, com 300\nsegundos; `IP_BANNED` no bloqueio temporário; e a rajada com intervalo\nmenor que 50 ms, que vem sem `Retry-After` e sem `retry_after`). A\nbusca de clientes tem limite próprio (`error` `rate_limited`, com\n`retry_after` e `detail`) e o laudo também (`error`\n`RATE_LIMIT_EXCEEDED`, 120 por hora por IP, `Retry-After: 3600`).\nNo sandbox, chamadas VASP autenticadas também usam\n`sandbox_simulator_rate_limit_exceeded`: 60 por minuto por key, 300 por\nminuto por empresa e 10.000 por dia UTC por empresa. Essa recusa inclui\n`Retry-After`, `X-RateLimit-Limit` e `X-RateLimit-Remaining: 0`.\nEspere o `Retry-After` quando ele vier.\n",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "description": "Teto da janela do limitador que respondeu, quando ele informa.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Requisições restantes na janela, quando o limitador informa.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ErrorResponseCoded"
                },
                {
                  "$ref": "#/components/schemas/ErroLimite"
                }
              ]
            }
          }
        }
      },
      "ErroInterno": {
        "description": "Falha inesperada. A mensagem é genérica de propósito; guarde o\n`X-Request-ID` da resposta para o suporte e repita com espera.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErroTexto"
            }
          }
        }
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Segundos a esperar antes de repetir. Vem nos 429 dos limitadores (salvo\na rajada do escudo) e no 503 de transação que conflitou (1 segundo).\n",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "schemas": {
      "ModuleNotEnabledResponse": {
        "type": "object",
        "required": [
          "success",
          "error",
          "code"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "example": "Este recurso não está habilitado no contrato atual."
          },
          "code": {
            "type": "string",
            "enum": [
              "MODULE_NOT_ENABLED"
            ]
          },
          "details": {
            "type": "object",
            "properties": {
              "module": {
                "type": "string",
                "description": "Código do módulo no catálogo.",
                "example": "mod_crypto_sanctions"
              }
            }
          }
        }
      },
      "GenericObjectResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CoafObjectivePreview": {
        "type": "object",
        "description": "Prévia objetiva do screening. Sem evento confirmado, o screening apenas devolve esta prévia, que nunca é comunicação definitiva.",
        "required": [
          "state",
          "final",
          "gaps"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "INDETERMINADA"
            ]
          },
          "final": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "gaps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ErrorResponseCoded": {
        "type": "object",
        "description": "Envelope de erro codificado (middlewares de API key e erros de\nnegócio da superfície VASP). Trate `error.code` como contrato\nestável; `error.message` é texto livre.\n",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "missing_api_key"
              },
              "message": {
                "type": "string",
                "example": "X-API-Key header required"
              }
            }
          }
        }
      },
      "ErroTexto": {
        "type": "object",
        "description": "Envelope de erro com a mensagem em texto no campo `error`, sem código.\nÉ o formato dos handlers de cadastro, carteira, webhooks, ledger e\nleituras regulatórias, da recuperação de falha inesperada (500), da\nindisponibilidade das camadas de borda (503) e da recusa do escudo de\nborda por padrão de ataque (403). Ramifique pelo status HTTP; a\nmensagem é texto livre e pode mudar.\n",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "example": "Carteira não encontrada."
          }
        }
      },
      "ErroTextoComCodigo": {
        "type": "object",
        "description": "Envelope de erro com a mensagem em texto em `error` e o código estável\nem `code`, na raiz do corpo. Na API de KYT aparece no corpo acima de\n1 MiB (`BODY_TOO_LARGE`, 413) e na leitura do módulo contratado que\nnão pôde ser feita (`MODULE_STORE_UNAVAILABLE`, 503).\n",
        "required": [
          "success",
          "error",
          "code"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "example": "BODY_TOO_LARGE"
          }
        }
      },
      "ErroLimiteDoLote": {
        "type": "object",
        "additionalProperties": false,
        "description": "Recusa dos limites próprios do lote de clientes, por ator (a chave de API\nque envia) ou por empresa, contados por hora. Vem com `Retry-After: 3600`.\n",
        "required": [
          "success",
          "error",
          "code",
          "retry",
          "window",
          "limit"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "BATCH_RATE_LIMIT_ACTOR",
              "BATCH_RATE_LIMIT_TENANT"
            ]
          },
          "retry": {
            "type": "string",
            "description": "Orientação em texto de quando repetir.",
            "example": "wait until next hour"
          },
          "window": {
            "type": "string",
            "example": "1 hour"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "description": "Teto de lotes por hora do escopo que recusou."
          }
        }
      },
      "ErroLimite": {
        "type": "object",
        "description": "Recusa de um limitador anterior à operação, com a mensagem em texto em\n`error`. `retry_after` repete, em segundos, o cabeçalho `Retry-After`\nquando o limitador o envia. `code` vem do escudo de borda\n(`RATE_LIMIT_HARD`, ou `IP_BANNED` com `reference` para citar ao\nsuporte), e `detail` vem dos limites próprios da busca de clientes e\ndo laudo.\n",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "example": "Too many requests. Please try again later."
          },
          "retry_after": {
            "type": "integer",
            "minimum": 1,
            "example": 3600
          },
          "code": {
            "type": "string",
            "example": "RATE_LIMIT_HARD"
          },
          "reference": {
            "type": "string",
            "description": "Identificador curto do bloqueio, para citar ao suporte."
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "VASPAlertListResponse": {
        "type": "object",
        "description": "Resposta de GET /vasp/alerts. A lista vem em `data` e a paginação em\n`pagination`; não há bloco `meta`.\n",
        "required": [
          "success",
          "data",
          "pagination"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VASPClientAlert"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "total",
              "limit",
              "offset"
            ],
            "properties": {
              "total": {
                "type": "integer",
                "minimum": 0,
                "description": "Total de alertas do filtro, em todas as páginas."
              },
              "limit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 200,
                "description": "Tamanho de página efetivamente usado."
              },
              "offset": {
                "type": "integer",
                "minimum": 0,
                "description": "Deslocamento efetivamente usado."
              }
            }
          }
        }
      },
      "VASPClientAlert": {
        "type": "object",
        "description": "Projeção do alerta para o cliente, sem identidades internas de operador,\nmetadados nem evidências. Campos opcionais são omitidos quando vazios.\n",
        "required": [
          "id",
          "type",
          "severity",
          "status",
          "title",
          "requires_sar",
          "requires_coaf",
          "sla_breached",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "rule_id": {
            "type": "string"
          },
          "risk_score": {
            "type": "number",
            "format": "double"
          },
          "requires_sar": {
            "type": "boolean"
          },
          "requires_coaf": {
            "type": "boolean"
          },
          "sla_deadline": {
            "type": "string",
            "format": "date-time"
          },
          "sla_breached": {
            "type": "boolean"
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "transaction_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "wallet_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VASPCustomerCreateRequest": {
        "type": "object",
        "required": [
          "external_id",
          "type"
        ],
        "description": "Envie o documento do titular em `document` (texto) ou em `document_hash`\n(SHA-256); um dos dois é obrigatório. `document_hash` continua aceito\npara quem já integra por ele, mas, sozinho, não permite exibir o\ndocumento. Se vierem os dois e divergirem, a resposta é 422\n`DOCUMENT_HASH_MISMATCH`.\n",
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid",
            "description": "Opcional. Id do cliente Sentinex que é o mesmo titular: o id devolvido por\nPOST /clients, o mesmo que a análise KYT recebe como client_id. É gravado uma vez e\nnão tem rota de alteração. Sem ele, o KYC externo deste cliente (convite e push)\nresponde 403 client_link_required. UUID zero ou texto que não é UUID responde 400\ninvalid_client_id; cliente que não é desta empresa responde 422 client_not_found.\n"
          },
          "external_id": {
            "type": "string",
            "maxLength": 128,
            "description": "Id do cliente no seu sistema. Padrão: letras, números, ponto, underscore, dois-pontos, hífen."
          },
          "type": {
            "type": "string",
            "enum": [
              "PF",
              "PJ"
            ]
          },
          "document_hash": {
            "type": "string",
            "description": "SHA-256 hex do CPF/CNPJ, com prefixo sha256 opcional. Continua\naceito. Envie `document_hash` ou `document`; um dos dois é\nobrigatório. Só com `document_hash`, o servidor não tem o documento\npara cifrar e não consegue exibi-lo em tela nem em laudo. Se vier\njunto de `document` e divergir, a resposta é 422\n`DOCUMENT_HASH_MISMATCH`. Neste campo, nunca envie o documento em\nclaro: para isso existe `document`.\n"
          },
          "document": {
            "type": "string",
            "writeOnly": true,
            "minLength": 11,
            "maxLength": 32,
            "description": "Opcional. CPF ou CNPJ do titular em texto, com ou sem pontuação (o\nCNPJ pode ser numérico ou alfanumérico). O servidor canoniza o valor\n(CPF só com os 11 dígitos; CNPJ em maiúsculas e sem pontuação) e\nconfere os dígitos verificadores. O servidor não guarda o documento\nem claro: guarda só um índice cego, calculado com uma chave da empresa\nmantida fora do banco, que serve para buscar e comparar, e a cifra do\nvalor, para exibição a quem tem permissão. Não é SHA-256 sem sal.\n`document` tem precedência no cálculo do índice. `document_hash`\ncontinua aceito; envie um dos dois. Se vierem os dois e divergirem,\na resposta é 422 `DOCUMENT_HASH_MISMATCH`. O documento nunca é\ndevolvido em resposta. Os exemplos deste contrato usam documentos\nfictícios.\n",
            "example": "123.456.789-01"
          },
          "document_country": {
            "type": "string",
            "description": "ISO-3166 alpha-2 (default BR)."
          },
          "legal_name_hash": {
            "type": "string",
            "description": "SHA-256 hex do nome/razão social (opcional)."
          },
          "trade_name": {
            "type": "string",
            "maxLength": 255
          },
          "incorporation_date": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD (PJ)."
          },
          "country": {
            "type": "string",
            "description": "ISO-3166 alpha-2 (default BR)."
          },
          "state": {
            "type": "string",
            "maxLength": 255
          },
          "kyc_tier": {
            "type": "string",
            "enum": [
              "SIMPLIFIED",
              "STANDARD",
              "ENHANCED"
            ],
            "description": "Default STANDARD."
          },
          "kyc_completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "external_kyc_provider": {
            "type": "string",
            "maxLength": 255
          },
          "external_kyc_reference_url": {
            "type": "string",
            "description": "URL https absoluta (máx. 2048 chars)."
          },
          "risk_classification": {
            "type": "string",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "CRITICAL"
            ],
            "description": "Default MEDIUM."
          },
          "tags": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[a-z0-9_]{1,32}$"
            }
          },
          "attributes": {
            "type": "object",
            "description": "JSON livre (máx. 16 KB). Chaves/valores com identificadores\npessoais em claro (cpf, cnpj, email, phone, nomes) são rejeitados\ncom 400.\n"
          },
          "consent_data_share": {
            "type": "boolean"
          },
          "consent_data_share_at": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "description": "Default api."
          },
          "source_event_id": {
            "type": "string",
            "maxLength": 255
          }
        }
      },
      "VASPCustomer": {
        "type": "object",
        "description": "Projeção retornada pelos endpoints /vasp/customers.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id do cliente, no formato UUID."
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "nullable": true,
            "description": "Vínculo com o cliente Sentinex, no formato UUID; nulo quando a matrícula não o informou."
          },
          "external_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "PF",
              "PJ"
            ]
          },
          "document_country": {
            "type": "string"
          },
          "trade_name": {
            "type": "string"
          },
          "incorporation_date": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "country": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "kyc_tier": {
            "type": "string",
            "enum": [
              "SIMPLIFIED",
              "STANDARD",
              "ENHANCED"
            ]
          },
          "kyc_completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "external_kyc_provider": {
            "type": "string"
          },
          "external_kyc_reference_url": {
            "type": "string"
          },
          "risk_classification": {
            "type": "string",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "CRITICAL"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "attributes": {
            "type": "object"
          },
          "consent_data_share": {
            "type": "boolean"
          },
          "consent_data_share_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "screening_status": {
            "type": "string"
          },
          "receita_status": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "source_event_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VASPCustomerBatchRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "customers"
        ],
        "properties": {
          "customers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "description": "De 1 a 1.000 clientes finais por chamada. Cada item tem o formato do\ncorpo de `POST /vasp/customers`.\n",
            "items": {
              "$ref": "#/components/schemas/VASPCustomerCreateRequest"
            }
          }
        }
      },
      "VASPCustomerBatchItemResult": {
        "type": "object",
        "description": "Resultado de um item do lote. `data` vem quando o status é `CREATED` ou\n`UPDATED`, e `error` vem quando é `REJECTED`. O resultado nunca revela se\no documento do item já existe sob outro `external_id`.\n",
        "required": [
          "index",
          "status"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0,
            "description": "Posição do item em `customers`, contada a partir de 0."
          },
          "external_id": {
            "type": "string",
            "description": "O `external_id` normalizado do titular. Vem só quando o status é\n`CREATED` ou `UPDATED`; item `REJECTED` não o repete (use `index`).\n"
          },
          "status": {
            "type": "string",
            "enum": [
              "CREATED",
              "UPDATED",
              "REJECTED"
            ],
            "description": "`CREATED` quando o titular é novo e foi gravado. `UPDATED` quando o\n`external_id` já existia na empresa e o titular foi atualizado por ele.\n`REJECTED` quando o item foi recusado por validação e nada foi\ngravado para ele.\n"
          },
          "data": {
            "$ref": "#/components/schemas/VASPCustomer"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estável de validação do item: `invalid_customer`,\n`invalid_client_id`, `client_not_found` ou\n`DOCUMENT_HASH_MISMATCH`. Trate código desconhecido como erro\ngenérico: a lista pode crescer. Nenhum código diz que o documento\njá existe sob outro `external_id`.\n"
              },
              "message": {
                "type": "string",
                "description": "Texto fixo, `Item recusado por validação.`; não detalha o campo."
              }
            }
          }
        }
      },
      "VASPCustomerBatchResponse": {
        "type": "object",
        "required": [
          "success",
          "idempotent_replay",
          "summary",
          "results"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Verdadeiro quando o lote foi lido e processado, mesmo que itens\ntenham sido recusados. Leia `results`.\n"
          },
          "idempotent_replay": {
            "type": "boolean",
            "description": "Sempre presente. Verdadeiro quando é o resultado original,\ndevolvido na repetição com a mesma `Idempotency-Key` e o mesmo\ncorpo; falso na primeira execução.\n"
          },
          "summary": {
            "type": "object",
            "required": [
              "total",
              "created",
              "updated",
              "rejected"
            ],
            "properties": {
              "total": {
                "type": "integer",
                "minimum": 0
              },
              "created": {
                "type": "integer",
                "minimum": 0
              },
              "updated": {
                "type": "integer",
                "minimum": 0
              },
              "rejected": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "results": {
            "type": "array",
            "description": "Um resultado por item, na ordem do envio.",
            "items": {
              "$ref": "#/components/schemas/VASPCustomerBatchItemResult"
            }
          }
        }
      },
      "VASPWalletEnrollRequest": {
        "type": "object",
        "required": [
          "customer_id",
          "external_id",
          "address",
          "blockchain"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "Id do cliente, no formato UUID, devolvido por POST /vasp/customers.\nValor que não é UUID responde 400 invalid_wallet.\n"
          },
          "external_id": {
            "type": "string",
            "maxLength": 128,
            "description": "Id da carteira no seu sistema (único por empresa)."
          },
          "address": {
            "type": "string",
            "maxLength": 256,
            "description": "Endereço da carteira, validado pela rede (endereço inválido\nresponde 400 invalid_wallet). O backend grava só a cifra do\nendereço e o HMAC da empresa; nunca o endereço em claro.\n"
          },
          "blockchain": {
            "type": "string",
            "description": "Aceitos: btc, eth, bsc, polygon, tron, solana, arbitrum (e aliases)."
          },
          "asset_symbol": {
            "type": "string",
            "maxLength": 20,
            "description": "Símbolo do ativo (A-Z, 0-9, ponto, underscore, hífen)."
          },
          "wallet_label": {
            "type": "string",
            "maxLength": 255
          },
          "custody_type": {
            "type": "string",
            "enum": [
              "vasp_custodied",
              "self_hosted"
            ],
            "description": "Custódia declarada pela VASP: `vasp_custodied` (a VASP custodia) ou\n`self_hosted` (autocustódia do cliente). Opcional: ausente fica\nregistrado como não informado e é contado como tal.\n"
          },
          "metadata": {
            "type": "object",
            "description": "JSON livre (máx. 16 KB), sem identificadores pessoais em claro."
          }
        }
      },
      "VASPWalletPatchRequest": {
        "type": "object",
        "description": "Pelo menos um de `status`, `address` ou `custody_type`.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "active"
            ],
            "description": "Reativa a carteira desativada, com o mesmo wallet_id."
          },
          "address": {
            "type": "string",
            "maxLength": 256,
            "description": "Reenvio do endereço da carteira; exige `blockchain`."
          },
          "blockchain": {
            "type": "string",
            "description": "Rede do endereço reenviado. Aceitos: btc, eth, bsc, polygon, tron, solana, arbitrum (e aliases)."
          },
          "custody_type": {
            "type": "string",
            "enum": [
              "vasp_custodied",
              "self_hosted"
            ]
          }
        }
      },
      "VASPWalletErrorResponse": {
        "type": "object",
        "description": "Envelope de erro da superfície de carteiras VASP. Além de `code` e\n`message`, os conflitos de carteira trazem os ids envolvidos.\n",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estável. Na superfície de carteiras: wallet_already_exists,\nKYT_IDEMPOTENCY_CONFLICT, wallet_is_institutional,\nwallet_duplicate_legacy, wallet_has_open_process,\nwallet_address_active_elsewhere. Trate código desconhecido como\nerro genérico: a lista pode crescer.\n"
              },
              "message": {
                "type": "string"
              },
              "wallet_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Carteiras antigas que casam o endereço (wallet_duplicate_legacy)."
              },
              "wallet_id": {
                "type": "string",
                "format": "uuid",
                "description": "A outra carteira ativa do endereço (wallet_address_active_elsewhere)."
              },
              "case_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Casos abertos ligados à carteira (wallet_has_open_process)."
              },
              "sar_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "SAR não enviados ligados à carteira (wallet_has_open_process)."
              }
            }
          }
        }
      },
      "VASPWalletEnrollment": {
        "type": "object",
        "description": "Resposta dos endpoints de enrollment de carteira.",
        "properties": {
          "wallet_id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          },
          "blockchain": {
            "type": "string"
          },
          "asset_symbol": {
            "type": "string"
          },
          "wallet_label": {
            "type": "string"
          },
          "screening_status": {
            "type": "string",
            "description": "PENDING, APPROVED, REVIEW, BLOCKED ou FAILED."
          },
          "decision": {
            "type": "string",
            "enum": [
              "APPROVE",
              "REVIEW",
              "BLOCK"
            ]
          },
          "risk_score": {
            "type": "number",
            "format": "float"
          },
          "risk_tier": {
            "type": "string"
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "existing_wallet": {
            "type": "boolean",
            "description": "true quando a carteira já existia na base da empresa."
          },
          "status_url": {
            "type": "string",
            "description": "Caminho para consultar o status (GET /vasp/wallets/{id})."
          },
          "alert_id": {
            "type": "string"
          },
          "alert_created": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "deactivated"
            ],
            "description": "Estado da carteira; a desativada mantém o wallet_id."
          },
          "custody_type": {
            "type": "string",
            "nullable": true,
            "enum": [
              "vasp_custodied",
              "self_hosted"
            ],
            "description": "Custódia declarada; null quando não informada."
          },
          "deactivated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "RuleCoverageDegradedCapability": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "rule_id",
          "source",
          "cause"
        ],
        "properties": {
          "rule_id": {
            "type": "string",
            "maxLength": 80
          },
          "source": {
            "type": "string",
            "maxLength": 80
          },
          "cause": {
            "type": "string",
            "enum": [
              "REQUIRED_INPUT_MISSING",
              "SOURCE_UNAVAILABLE",
              "SOURCE_STALE",
              "MANIFEST_MISSING",
              "ENGINE_UNAVAILABLE"
            ]
          }
        }
      },
      "RuleCoverageSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "manifest_version",
          "state",
          "evaluable_rules",
          "degraded_rules",
          "disabled_rules",
          "causes"
        ],
        "properties": {
          "manifest_version": {
            "type": "string",
            "enum": [
              "snx-rule-capabilities/2026-07-29.v1"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "ACTIVE_READY",
              "ACTIVE_DEGRADED",
              "DISABLED"
            ]
          },
          "input_assessments": {
            "type": "object",
            "description": "Estado das entradas `amount_brl`, `aggregate_7d` e `travel_rule` da triagem em tempo real, presente só na resposta de `POST /vasp/screenings`. Sem identificação do titular (`customer_ref` vazio), o agregado de 7 dias sai `NOT_EVALUATED` e não impõe retenção. Omitido em análises anteriores a esta medição.",
            "additionalProperties": {
              "type": "object",
              "required": [
                "state"
              ],
              "properties": {
                "state": {
                  "type": "string",
                  "enum": [
                    "MEASURED",
                    "NOT_MEASURED",
                    "NOT_EVALUATED"
                  ]
                },
                "reason": {
                  "type": "string",
                  "description": "Motivo, presente quando o estado não é `MEASURED`."
                }
              }
            }
          },
          "evaluable_rules": {
            "type": "array",
            "maxItems": 512,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "degraded_rules": {
            "type": "array",
            "maxItems": 512,
            "items": {
              "$ref": "#/components/schemas/RuleCoverageDegradedCapability"
            }
          },
          "disabled_rules": {
            "type": "array",
            "maxItems": 512,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "causes": {
            "type": "array",
            "maxItems": 16,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "REQUIRED_INPUT_MISSING",
                "SOURCE_UNAVAILABLE",
                "SOURCE_STALE",
                "MANIFEST_MISSING",
                "ENGINE_UNAVAILABLE"
              ]
            }
          }
        }
      },
      "KYTMeasurementState": {
        "type": "string",
        "enum": [
          "MEASURED",
          "NOT_MEASURED"
        ],
        "description": "Estado de medição de uma dimensão. `NOT_MEASURED` significa que a dimensão não foi medida nesta consulta, e nunca vale como resultado negativo, zero ou lista vazia conclusiva."
      },
      "KYTIssuerBlockLabel": {
        "type": "object",
        "required": [
          "source",
          "issuer",
          "event",
          "state",
          "contract",
          "block_number",
          "block_time",
          "tx_hash",
          "effect"
        ],
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "ISSUER_BLOCK_EVENT"
            ]
          },
          "issuer": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ]
          },
          "event": {
            "type": "string",
            "description": "Assinatura do evento do ABI público do contrato.",
            "example": "Blacklisted(address)"
          },
          "state": {
            "type": "string",
            "enum": [
              "BLOCKED",
              "UNBLOCKED"
            ],
            "description": "Estado pelo evento mais recente, por (bloco, índice do log)."
          },
          "contract": {
            "type": "string",
            "pattern": "^0x[0-9a-f]{40}$",
            "description": "Contrato do emissor que gravou o evento."
          },
          "block_number": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "block_time": {
            "type": "string",
            "format": "date-time",
            "description": "Data do bloco que contém o evento, em UTC."
          },
          "tx_hash": {
            "type": "string",
            "pattern": "^0x[0-9a-f]{64}$"
          },
          "effect": {
            "type": "string",
            "enum": [
              "LABEL_ONLY"
            ]
          },
          "recommendation": {
            "type": "string",
            "enum": [
              "REVIEW",
              "HOLD"
            ],
            "description": "Recomendação da política da empresa quando `state` é `BLOCKED`. Nunca `DENY` e nunca `ALLOW`. Omitido quando `state` é `UNBLOCKED`."
          }
        }
      },
      "KYTMeasuredCapabilities": {
        "type": "object",
        "description": "Capacidade de medição de cada dimensão da triagem, independente do status do provedor. `MEASURED` atesta apenas a capacidade nomeada, inclusive quando o resultado foi negativo. Os cinco campos saem sempre, com `NOT_MEASURED` quando a dimensão não foi medida. `labels` vale `MEASURED` também quando há etiqueta de bloqueio de emissor para o endereço (ver `label_evidence`). Ausência de evento não torna a dimensão `MEASURED`.",
        "required": [
          "sanctions",
          "labels",
          "exposures",
          "graph",
          "typologies"
        ],
        "properties": {
          "sanctions": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          },
          "labels": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          },
          "exposures": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          },
          "graph": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          },
          "typologies": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          }
        }
      },
      "KYTSanctionEvidence": {
        "type": "object",
        "description": "Fato de sanção verificado na base autorizada, distinto de rótulo ou score. Categorias adversas não provam sanção. `designation_start` e `designation_end` saem omitidos quando não foram medidos, e a data de publicação do dataset nunca preenche o início jurídico da designação.",
        "required": [
          "subject",
          "address",
          "list",
          "list_version",
          "list_date",
          "match_type",
          "source",
          "observed_at"
        ],
        "properties": {
          "subject": {
            "type": "string",
            "enum": [
              "counterparty"
            ]
          },
          "address": {
            "type": "string",
            "description": "Endereço consultado ao qual o fato se vincula."
          },
          "list": {
            "type": "string",
            "example": "OPENSANCTIONS",
            "description": "Lista de origem do fato. Nos fixtures do sandbox vale `SANDBOX_FIXTURE`."
          },
          "list_version": {
            "type": "string",
            "description": "Versão da lista fornecida pela fonte. Nos fixtures do sandbox é o identificador do catálogo `sandbox-fixtures-v1`."
          },
          "list_date": {
            "type": "string",
            "format": "date-time",
            "description": "Data fornecida pela lista, e não o horário da consulta."
          },
          "match_type": {
            "type": "string",
            "enum": [
              "EXACT_ADDRESS"
            ]
          },
          "source": {
            "type": "string",
            "description": "Origem do fato. Em produção vale `SENTINEX_SANCTIONS_DATABASE`; nos fixtures do sandbox vale `SENTINEX_SANDBOX`."
          },
          "observed_at": {
            "type": "string",
            "format": "date-time",
            "description": "Instante da consulta que observou o fato."
          },
          "program": {
            "type": "string",
            "description": "Programa da designação na fonte oficial. Omitido quando não medido."
          },
          "designation_start": {
            "type": "string",
            "format": "date-time",
            "description": "Início inclusivo da designação, em UTC. Omitido enquanto a fonte não fornecer o intervalo; os produtores atuais ainda não o preenchem."
          },
          "designation_end": {
            "type": "string",
            "format": "date-time",
            "description": "Fim exclusivo da designação, quando informado pela fonte. Omitido enquanto a fonte não fornecer o intervalo."
          }
        }
      },
      "SanctionsSourceCoverage": {
        "type": "object",
        "description": "Estado de uma fonte de sanções configurada na consulta. Dado não medido não publica contagem zero nem data zero. A cobertura de fontes é independente da cobertura do motor de regras (`coverage`).",
        "required": [
          "source",
          "state"
        ],
        "properties": {
          "source": {
            "type": "string",
            "example": "OPENSANCTIONS"
          },
          "state": {
            "$ref": "#/components/schemas/KYTMeasurementState"
          },
          "dataset": {
            "type": "string"
          },
          "dataset_version": {
            "type": "string"
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "description": "Data de publicação informada pela fonte."
          },
          "loaded_at": {
            "type": "string",
            "format": "date-time",
            "description": "Última leitura completa da fonte."
          },
          "count": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "description": "Quantidade de endereços da versão. Omitido quando não medido."
          },
          "reason": {
            "type": "string",
            "description": "Motivo de indisponibilidade, como `stale` ou `snapshot_unavailable`."
          }
        }
      },
      "KYTScoreLedger": {
        "type": "object",
        "description": "Reconstrução do score. Comece por `base`, aplique cada item sobre o valor `before` para obter `after` (`effective` é `after` menos `before`), limite o resultado ao intervalo `minimum` a `maximum` e quantize uma única vez. O resultado é `final`, igual ao `risk_score` da resposta. O item de origem `deep_lane_adjustment`, quando presente, explicita a diferença entre o impacto total devolvido pelo avaliador de regras e as contribuições individuais disponíveis.",
        "required": [
          "base",
          "items",
          "minimum",
          "maximum",
          "quantization",
          "final"
        ],
        "properties": {
          "base": {
            "type": "number"
          },
          "minimum": {
            "type": "number",
            "enum": [
              0
            ]
          },
          "maximum": {
            "type": "number",
            "enum": [
              100
            ]
          },
          "quantization": {
            "type": "string",
            "enum": [
              "HALF_UP"
            ]
          },
          "final": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "source",
                "operation",
                "before",
                "operand",
                "effective",
                "after"
              ],
              "properties": {
                "source": {
                  "type": "string"
                },
                "operation": {
                  "type": "string",
                  "enum": [
                    "MAX",
                    "ADD",
                    "FLOOR"
                  ]
                },
                "before": {
                  "type": "number"
                },
                "operand": {
                  "type": "number"
                },
                "effective": {
                  "type": "number"
                },
                "after": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "KYTShadowObservation": {
        "type": "object",
        "description": "Observação de regra em modo sombra. Não entra no score nem nos hits que sustentam a recomendação, e leva o `screening_id` criado antes da avaliação.",
        "required": [
          "screening_id",
          "rule_id",
          "rule_name",
          "triggered",
          "score_impact"
        ],
        "properties": {
          "screening_id": {
            "type": "string",
            "format": "uuid"
          },
          "rule_id": {
            "type": "string"
          },
          "rule_name": {
            "type": "string"
          },
          "triggered": {
            "type": "boolean"
          },
          "score_impact": {
            "type": "number"
          }
        }
      },
      "VASPScreeningDecision": {
        "type": "object",
        "required": [
          "screening_id",
          "screen_status",
          "recommendation",
          "client_decision",
          "risk_score",
          "risk_level",
          "sanctions",
          "rule_hits",
          "coverage",
          "obligations",
          "amount_brl",
          "fail_closed",
          "latency_ms",
          "evaluated_at",
          "api_version"
        ],
        "properties": {
          "screening_id": {
            "type": "string",
            "format": "uuid"
          },
          "operation_at": {
            "type": "string",
            "format": "date-time",
            "description": "Data efetivamente usada na consulta de sanção por intervalo de designação, em UTC. Análise gravada antes desta data existir não traz o campo."
          },
          "operation_time_source": {
            "type": "string",
            "enum": [
              "OPERATION_AT",
              "BLOCK_TIME",
              "RECEIVED_AT"
            ],
            "description": "Origem de `operation_at`, em ordem de precedência: `OPERATION_AT` (enviada pelo cliente), `BLOCK_TIME` (data do bloco da transação, quando o servidor já a mediu) e `RECEIVED_AT` (instante em que a requisição foi recebida)."
          },
          "client_transaction_id": {
            "type": "string"
          },
          "customer_id": {
            "type": "string",
            "format": "uuid",
            "description": "UUID informado na análise original. Presente junto de `holder_resolution`, e ausente quando a requisição não trouxe `customer_id`."
          },
          "screen_status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "recommendation": {
            "type": "string",
            "enum": [
              "ALLOW",
              "REVIEW",
              "HOLD",
              "DENY"
            ],
            "description": "Recomendação auxiliar da Sentinex. Não é decisão operacional do cliente.\n`DENY` somente com base em sanção ou por regra de bloqueio configurada\npela própria empresa; os demais riscos saem `REVIEW`.\n",
            "example": "REVIEW"
          },
          "client_decision": {
            "$ref": "#/components/schemas/KYTClientDecision"
          },
          "risk_score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "example": 70,
            "description": "Score final limitado a 0 a 100 e arredondado uma única vez, com meio para cima. É o mesmo valor de `score_ledger.final` e do registro escalar da análise. A repetição idempotente e a consulta devolvem o snapshot gravado na análise original."
          },
          "risk_level": {
            "type": "string",
            "description": "Faixa de risco em caixa alta.",
            "example": "HIGH"
          },
          "sanctions": {
            "type": "object",
            "required": [
              "hit"
            ],
            "description": "Resultado da triagem de sanções da contraparte. `hit` e `state` distinguem ausência de medição de resultado negativo: `hit` só sai booleano quando `state` é `MEASURED`, e sai nulo quando a sanção não foi medida. Categorias adversas não provam sanção.",
            "properties": {
              "hit": {
                "type": "boolean",
                "nullable": true,
                "description": "Nulo quando `state` é `NOT_MEASURED`. Falso com `state` `MEASURED` é consulta negativa medida; nulo nunca vale como limpo."
              },
              "state": {
                "$ref": "#/components/schemas/KYTMeasurementState"
              },
              "categories": {
                "type": "array",
                "description": "Ausente quando vazio, nunca nulo.",
                "items": {
                  "type": "string"
                }
              },
              "provider": {
                "type": "string"
              },
              "provider_status": {
                "type": "string"
              },
              "evidence": {
                "type": "array",
                "nullable": true,
                "description": "Fatos de sanção verificados para a contraparte. Nulo ou vazio quando não há fato verificado.",
                "items": {
                  "$ref": "#/components/schemas/KYTSanctionEvidence"
                }
              },
              "coverage": {
                "type": "array",
                "nullable": true,
                "description": "Estado de cada fonte de sanções configurada nesta consulta.",
                "items": {
                  "$ref": "#/components/schemas/SanctionsSourceCoverage"
                }
              }
            }
          },
          "capabilities": {
            "$ref": "#/components/schemas/KYTMeasuredCapabilities"
          },
          "label_evidence": {
            "type": "array",
            "description": "Etiquetas de bloqueio de emissor de stablecoin (USDT e USDC) lidas de eventos públicos do contrato. Omitido quando nenhum evento foi coletado para o endereço: ausência de evento não prova que o endereço esteja livre. Efeito sempre `LABEL_ONLY`, nunca sanção, nunca score.",
            "items": {
              "$ref": "#/components/schemas/KYTIssuerBlockLabel"
            }
          },
          "direct_exposure": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "number"
            },
            "description": "Exposição direta por categoria. Nulo quando `capabilities.exposures` é `NOT_MEASURED`, sem percentual fabricado a partir de rótulo."
          },
          "indirect_exposure": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "number"
            },
            "description": "Exposição indireta por categoria. Nulo quando `capabilities.exposures` é `NOT_MEASURED`."
          },
          "rule_hits": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "rule_id",
                "name",
                "severity",
                "weight"
              ],
              "properties": {
                "rule_id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "severity": {
                  "type": "string"
                },
                "weight": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          "coverage": {
            "$ref": "#/components/schemas/RuleCoverageSnapshot"
          },
          "not_covered": {
            "type": "array",
            "description": "Dimensões que esta versão não analisa por desenho. Cada item traz `rule_id`, `source` e `cause`. Triagens gravadas antes desta versão não trazem o campo. `ALLOW` com entradas aqui significa que aquelas dimensões não foram analisadas, e não que o resultado delas foi negativo. Falha real do provedor, timeout ou erro de consulta continua em `coverage.degraded_rules` e retém a triagem; não entra neste campo.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "rule_id",
                "source",
                "cause"
              ],
              "properties": {
                "rule_id": {
                  "type": "string",
                  "maxLength": 80
                },
                "source": {
                  "type": "string",
                  "maxLength": 80
                },
                "cause": {
                  "type": "string",
                  "enum": [
                    "CAPABILITY_NOT_OFFERED",
                    "NETWORK_NOT_COVERED"
                  ]
                }
              }
            }
          },
          "obligations": {
            "type": "object",
            "required": [
              "travel_rule",
              "coaf_sar",
              "decripto"
            ],
            "properties": {
              "travel_rule": {
                "type": "string",
                "enum": [
                  "UNKNOWN",
                  "REQUIRED",
                  "NOT_REQUIRED"
                ]
              },
              "coaf_sar": {
                "type": "string",
                "description": "Campo legado. O motor devolve `MONITOR` e não produz `REPORTABLE` por limiar monetário, portanto o valor não comprova obrigação objetiva de comunicação nem decisão de comunicar suspeição. Use `coaf_objective` para o estado objetivo (prévia)."
              },
              "decripto": {
                "type": "string"
              },
              "coaf_objective": {
                "$ref": "#/components/schemas/CoafObjectivePreview"
              }
            }
          },
          "amount_brl": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "Valor em reais, não em centavos, calculado pelo servidor. Nulo quando a quantidade ou a cotação não estão disponíveis ou a cotação está vencida, o que retém a análise em `HOLD` temporário.",
            "example": 7500.5
          },
          "declared_fiat_value_brl": {
            "type": "number",
            "format": "double",
            "nullable": true,
            "description": "Valor declarado pelo integrador em `fiat_value_brl`, preservado para conferência. Não decide regras nem recomendação."
          },
          "score_ledger": {
            "$ref": "#/components/schemas/KYTScoreLedger"
          },
          "shadow_observations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KYTShadowObservation"
            }
          },
          "fail_closed": {
            "type": "boolean"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "latency_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Latência total em milissegundos."
          },
          "evaluated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Data e hora em RFC 3339, UTC.",
            "example": "2026-09-21T13:40:45Z"
          },
          "api_version": {
            "type": "string",
            "enum": [
              "v1"
            ]
          },
          "holder_resolution": {
            "type": "string",
            "enum": [
              "RESOLVED",
              "NOT_FOUND"
            ],
            "description": "Obrigatório na resposta de sucesso quando a requisição contém\n`customer_id`, independentemente do adicional. `RESOLVED` significa\nque o titular pertence à empresa autenticada; não atesta enriquecimento.\n`NOT_FOUND` inclui UUID desconhecido e UUID de outra empresa, sem\nalterar recomendação, score, motivos ou cobrança. Com cruzamento\ndesligado, nenhuma fonte de KYC é lida. Sem `customer_id`, o campo\nnão vem.\n"
          },
          "transaction": {
            "type": "object",
            "description": "Presente somente depois de `PATCH /vasp/screenings/{id}/tx-hash`. O hash\nvindo no `POST` original não gera este objeto.\n",
            "properties": {
              "tx_hash": {
                "type": "string"
              },
              "attached_at": {
                "type": "string",
                "format": "date-time"
              },
              "origin": {
                "type": "string",
                "enum": [
                  "TX_HASH_PATCH"
                ]
              }
            }
          }
        }
      },
      "VASPScreeningLegacyDecision": {
        "type": "object",
        "description": "Projeção mínima para linha N-1 sem snapshot de resposta.",
        "required": [
          "screening_id",
          "recommendation",
          "client_decision",
          "risk_score",
          "risk_level",
          "amount_brl",
          "created_at",
          "api_version"
        ],
        "properties": {
          "screening_id": {
            "type": "string",
            "format": "uuid"
          },
          "recommendation": {
            "type": "string",
            "enum": [
              "ALLOW",
              "REVIEW",
              "HOLD",
              "DENY"
            ]
          },
          "client_decision": {
            "$ref": "#/components/schemas/KYTClientDecision"
          },
          "risk_score": {
            "type": "integer"
          },
          "risk_level": {
            "type": "string"
          },
          "amount_brl": {
            "type": "number",
            "format": "double"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "api_version": {
            "type": "string",
            "enum": [
              "v1"
            ]
          }
        }
      },
      "KYTClientDecision": {
        "type": "object",
        "required": [
          "status"
        ],
        "description": "Decisão e desfecho atribuídos exclusivamente ao cliente.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "PENDING_CLIENT_DECISION",
              "RECORDED"
            ]
          },
          "authority": {
            "type": "string",
            "enum": [
              "CLIENT_EXCHANGE"
            ]
          },
          "operational_action": {
            "type": "string",
            "enum": [
              "ALLOW",
              "HOLD",
              "DENY"
            ]
          },
          "suspicion_outcome": {
            "type": "string",
            "enum": [
              "SUSPICIOUS",
              "NOT_SUSPICIOUS"
            ]
          },
          "rationale": {
            "type": "string"
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time",
            "description": "Data e hora em RFC 3339, UTC."
          }
        }
      },
      "KYTTxHashAttachRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tx_hash"
        ],
        "properties": {
          "tx_hash": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Hash da transação transmitida. O formato é validado conforme a rede do screening (EVM: 32 bytes em hex, com ou sem `0x`, gravado com `0x` e minúsculo; Tron, Bitcoin e demais redes de hash hex: 64 hex minúsculo sem `0x`; Solana: base58 de 64 a 90 caracteres com a caixa preservada; outras redes: identificador opaco de 32 a 128 caracteres). A grafia canônica é a gravada e a devolvida.\n"
          }
        }
      },
      "KYTTxHashAttachResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "idempotent_replay": {
            "type": "boolean",
            "description": "Presente e verdadeiro quando o hash já estava gravado e é o mesmo."
          },
          "data": {
            "type": "object",
            "required": [
              "screening_id",
              "tx_hash",
              "tx_hash_state",
              "origin",
              "recorded_at",
              "on_chain_verification",
              "recommendation",
              "client_decision"
            ],
            "properties": {
              "screening_id": {
                "type": "string",
                "format": "uuid"
              },
              "tx_hash": {
                "type": "string"
              },
              "tx_hash_state": {
                "type": "string",
                "enum": [
                  "ATTACHED"
                ]
              },
              "origin": {
                "type": "string",
                "enum": [
                  "TX_HASH_PATCH",
                  "SCREENING_REQUEST"
                ],
                "description": "`TX_HASH_PATCH` quando o hash veio deste anexo; `SCREENING_REQUEST` quando já veio no `POST /vasp/screenings`.\n"
              },
              "recorded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Instante do anexo; nulo quando o hash veio no POST original."
              },
              "on_chain_verification": {
                "type": "string",
                "enum": [
                  "NOT_MEASURED"
                ],
                "description": "A existência da transação na rede não é medida pela Sentinex neste passo."
              },
              "recommendation": {
                "type": "string",
                "enum": [
                  "ALLOW",
                  "REVIEW",
                  "HOLD",
                  "DENY"
                ],
                "description": "A recomendação original, inalterada."
              },
              "client_decision": {
                "$ref": "#/components/schemas/KYTClientDecision"
              }
            }
          }
        }
      },
      "KYTClientDecisionRequest": {
        "type": "object",
        "required": [
          "client_decision"
        ],
        "properties": {
          "client_decision": {
            "type": "object",
            "required": [
              "operational_action",
              "suspicion_outcome"
            ],
            "properties": {
              "operational_action": {
                "type": "string",
                "enum": [
                  "ALLOW",
                  "HOLD",
                  "DENY"
                ]
              },
              "suspicion_outcome": {
                "type": "string",
                "enum": [
                  "SUSPICIOUS",
                  "NOT_SUSPICIOUS"
                ]
              },
              "rationale": {
                "type": "string",
                "maxLength": 4000
              }
            }
          }
        }
      },
      "KYTClientDecisionResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "data": {
            "type": "object",
            "required": [
              "screening_id",
              "recommendation",
              "client_decision"
            ],
            "properties": {
              "screening_id": {
                "type": "string",
                "format": "uuid"
              },
              "recommendation": {
                "type": "string",
                "enum": [
                  "ALLOW",
                  "REVIEW",
                  "HOLD",
                  "DENY"
                ],
                "description": "Recomendação original da Sentinex, que não é alterada pela decisão do cliente."
              },
              "client_decision": {
                "$ref": "#/components/schemas/KYTClientDecision"
              }
            }
          }
        }
      },
      "KYTReportRequest": {
        "type": "object",
        "required": [
          "wallet"
        ],
        "properties": {
          "profile": {
            "type": "string",
            "enum": [
              "operational",
              "complete",
              "regulatory"
            ]
          },
          "wallet": {
            "type": "object",
            "required": [
              "address",
              "blockchain"
            ],
            "properties": {
              "address": {
                "type": "string",
                "minLength": 1
              },
              "blockchain": {
                "type": "string",
                "description": "Identificador da rede em minúsculas.",
                "example": "ethereum"
              }
            }
          }
        }
      },
      "KYTReportDocumentV2": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "report_id",
          "case_id",
          "profile",
          "metadata",
          "sections"
        ],
        "description": "Projeção do perfil emitido, sem o snapshot factual privado. Não é compatível com KYTReportJSON v1.",
        "properties": {
          "report_id": {
            "type": "string"
          },
          "case_id": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "enum": [
              "operacional",
              "completo",
              "regulatorio"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "sections": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "narrative",
                "tables"
              ],
              "properties": {
                "narrative": {
                  "type": "object",
                  "additionalProperties": true
                },
                "tables": {
                  "type": "array",
                  "nullable": true,
                  "items": {
                    "type": "object",
                    "properties": {
                      "headers": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "rows": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "graph": {
            "type": "object",
            "additionalProperties": true,
            "description": "Grafo público, omitido no perfil operacional."
          }
        }
      },
      "KYTReportJSON": {
        "type": "object",
        "required": [
          "api_version",
          "service_role",
          "report",
          "case",
          "verdict",
          "conclusion",
          "client_decision"
        ],
        "description": "Laudo KYT estruturado. Campos monetários estão em unidades decimais e datas\nusam RFC 3339 UTC.\n\n`report.id` é o identificador único da emissão (ULID, cujo instante é o da\nemissão) e é o mesmo do nome do arquivo e da auditoria. `case.case_id` é o\nagrupador estável da carteira e vem separado, na forma\n`KYT-WALLET-<REDE>-<hash do endereço canônico>`, com a rede em maiúsculas no\nnome canônico (`ETHEREUM`, não o apelido digitado) e o hash sobre o endereço\nna forma canônica: grafias do mesmo endereço (checksum ou minúsculo) e da\nmesma rede dão o mesmo `case_id`. Compatibilidade: laudos emitidos antes da\n2.16.0 e lidos do histórico trazem o `case_id` na forma antiga, calculada\nsobre a rede e o endereço como digitados, e `report.id` igual ao `case_id`.\nA mesma carteira pode, por isso, aparecer em dois grupos (um antigo e um\nnovo) até o histórico anterior expirar. Não una os dois pelo texto do\n`case_id`.\n\nValores em reais (`*_brl`) só existem com cotação completa. Sem ela,\n`scope.brl_status` é `nao_cotado` e nenhum campo em reais aparece no JSON:\ntrate a ausência como \"não cotado\", nunca como zero.\n",
        "properties": {
          "api_version": {
            "type": "string",
            "enum": [
              "v1"
            ]
          },
          "service_role": {
            "type": "string",
            "enum": [
              "AUXILIARY_ANALYSIS_SERVICE"
            ]
          },
          "report": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$",
                "description": "Identificador único da emissão (ULID). Até a 2.15.x repetia o\n`case_id`.\n"
              },
              "case_id": {
                "type": "string",
                "description": "Mesmo valor de `case.case_id`."
              }
            }
          },
          "case": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "case_id": {
                "type": "string",
                "example": "KYT-WALLET-ETHEREUM-0000000000000000"
              }
            }
          },
          "scope": {
            "$ref": "#/components/schemas/KYTReportScope"
          },
          "monitoring": {
            "$ref": "#/components/schemas/KYTReportMonitoring"
          },
          "attribution": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "vasp": {
                  "type": "boolean",
                  "nullable": true,
                  "description": "`true`, `false` ou `null` (não determinado). Contraparte observada\non-chain sem atribuição de entidade traz `null`: nunca leia\nausência de classificação como \"não é VASP\".\n"
                }
              }
            }
          },
          "exposure_summary": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "direct_brl": {
                  "type": "number",
                  "format": "double"
                },
                "indirect_brl": {
                  "type": "number",
                  "format": "double"
                },
                "total_brl": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          "hop_ladder": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "volume_brl": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          "source_of_funds": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "value_brl": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          "destination_of_funds": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "value_brl": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          "verdict": {
            "type": "object",
            "required": [
              "recommendation",
              "risk_tier",
              "score_total"
            ],
            "properties": {
              "recommendation": {
                "type": "string",
                "enum": [
                  "ALLOW",
                  "REVIEW",
                  "HOLD",
                  "DENY"
                ]
              },
              "risk_tier": {
                "type": "string"
              },
              "score_total": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              }
            },
            "additionalProperties": true
          },
          "conclusion": {
            "type": "object",
            "required": [
              "recommendation"
            ],
            "properties": {
              "recommendation": {
                "type": "string",
                "enum": [
                  "ALLOW",
                  "REVIEW",
                  "HOLD",
                  "DENY"
                ]
              }
            },
            "additionalProperties": true
          },
          "client_decision": {
            "$ref": "#/components/schemas/KYTClientDecision"
          }
        },
        "additionalProperties": true
      },
      "KYTReportScope": {
        "type": "object",
        "additionalProperties": true,
        "description": "Escopo do laudo (carteiras, redes, janela e valores).",
        "properties": {
          "brl_status": {
            "type": "string",
            "enum": [
              "medido",
              "nao_cotado"
            ],
            "description": "Estado de TODOS os valores em reais do laudo. `medido` exige cotação\ncompleta (`brl_quote`). Em `nao_cotado`, nenhum campo `*_brl`\n(`volume_brl`, `direct_brl`, `indirect_brl`, `total_brl`, `value_brl`)\nsai no JSON e o documento imprime `NÃO COTADO`, nunca `R$ 0,00`.\n"
          },
          "brl_quote": {
            "$ref": "#/components/schemas/KYTReportBRLQuote"
          },
          "volume_brl": {
            "type": "number",
            "format": "double",
            "description": "Ausente quando `brl_status` é `nao_cotado`."
          }
        }
      },
      "KYTReportBRLQuote": {
        "type": "object",
        "description": "Cotação que sustenta os valores em reais. Presente somente com os quatro\nelementos; faltando qualquer um, `brl_status` é `nao_cotado`.\n",
        "required": [
          "origin_currency",
          "rate",
          "source",
          "quoted_at"
        ],
        "properties": {
          "origin_currency": {
            "type": "string"
          },
          "rate": {
            "type": "string",
            "description": "Taxa em texto decimal, sem arredondamento."
          },
          "source": {
            "type": "string"
          },
          "quoted_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KYTReportMonitoring": {
        "type": "object",
        "description": "Estado de monitoramento da carteira comprovado por uma fonte nesta emissão.\nAusente significa que nenhuma fonte o comprovou, e o documento imprime\n`NÃO COMPROVADO NESTA EMISSÃO` em vez de afirmar monitoramento contínuo.\n",
        "required": [
          "status",
          "source",
          "observed_at"
        ],
        "properties": {
          "status": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KYTIdentityUnavailableResponse": {
        "description": "Identidade indisponível: o envelope público de erro acrescido de `data.recommendation` `HOLD`\ne `data.fail_closed` `true`. Nenhuma análise é criada, nenhuma regra é avaliada e nenhum consumo é registrado.\n",
        "type": "object",
        "required": [
          "success",
          "error",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "data": {
            "type": "object",
            "required": [
              "recommendation",
              "fail_closed"
            ],
            "properties": {
              "recommendation": {
                "type": "string",
                "enum": [
                  "HOLD"
                ]
              },
              "fail_closed": {
                "type": "boolean",
                "enum": [
                  true
                ]
              }
            }
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "http_status",
              "message",
              "action",
              "retriable",
              "retry_after_seconds"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "KYT_IDENTITY_UNAVAILABLE"
                ]
              },
              "http_status": {
                "type": "integer",
                "enum": [
                  503
                ]
              },
              "message": {
                "type": "string"
              },
              "action": {
                "type": "string"
              },
              "retriable": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "retry_after_seconds": {
                "type": "integer",
                "enum": [
                  30
                ]
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "KYTPublicErrorResponse": {
        "type": "object",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "http_status",
              "message",
              "action",
              "retriable"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "KYT_DEPENDENCY_UNAVAILABLE"
              },
              "http_status": {
                "type": "integer",
                "example": 503
              },
              "message": {
                "type": "string"
              },
              "action": {
                "type": "string"
              },
              "retriable": {
                "type": "boolean"
              },
              "retry_after_seconds": {
                "type": "integer",
                "minimum": 1
              },
              "request_id": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "address_chain_mismatch"
                ],
                "description": "Causa do 400 `KYT_WALLET_INVALID` no laudo: `address_chain_mismatch` diz\nque o endereço não é válido para a rede informada (rede desconhecida,\nchecksum inválido ou forma de outra rede). O par é validado antes de\nqualquer consulta externa. Ausente nos demais erros.\n"
              }
            }
          }
        }
      },
      "VASPScreeningGetResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "data": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VASPScreeningDecision"
              },
              {
                "$ref": "#/components/schemas/VASPScreeningLegacyDecision"
              }
            ]
          }
        }
      },
      "TenantAccessRefusalResponse": {
        "type": "object",
        "required": [
          "success",
          "error",
          "code"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "example": "Seu período de teste terminou. Fale com a Sentinex Risk para estender o teste ou contratar um plano."
          },
          "code": {
            "type": "string",
            "enum": [
              "TRIAL_EXPIRED",
              "TENANT_SUSPENDED",
              "TENANT_NOT_OPERATIONAL",
              "API_ACCESS_DISABLED",
              "TENANT_ACCESS_DENIED",
              "TENANT_ACCESS_STATE_UNAVAILABLE"
            ]
          },
          "details": {
            "type": "object",
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "TRIAL_WITHOUT_END_DATE",
                  "TRIAL_ENDED",
                  "POC_TRIAL_RETIRED",
                  "DEMO_WITHOUT_COMMERCIAL_ACCESS",
                  "NO_COMMERCIAL_ACCESS"
                ],
                "description": "Presente em `TRIAL_EXPIRED`."
              },
              "trial_ends_at": {
                "type": "string",
                "format": "date-time",
                "description": "Presente em `TRIAL_EXPIRED` quando o trial tem data."
              },
              "lifecycle_state": {
                "type": "string",
                "description": "Presente em `empresa_NOT_OPERATIONAL`."
              }
            }
          }
        }
      }
    }
  }
}
