{
  "openapi": "3.0.3",
  "info": {
    "title": "ProofSnap API",
    "version": "2.0.0",
    "license": {
      "name": "Proprietary",
      "url": "https://getproofsnap.com/terms.html"
    },
    "description": "A ProofSnap API carrega uma página web em um navegador Chromium no servidor e retorna um pacote de provas (ZIP).\n\nCada pacote de captura contém a captura de tela, o HTML da página, o texto extraído da página, metadata.json, forensic_log.json, chain_of_custody.json, evidence.pdf e um manifest.json que lista o SHA-256 de cada arquivo. O manifest.json é assinado com uma chave RSA-4096 gerada para aquela captura (a chave pública é entregue como publickey.pem, a chave privada é descartada) e o SHA-256 do manifest.json é enviado a calendários OpenTimestamps para uma ancoragem em Bitcoin (manifest.json.ots). Se a requisição ao calendário falhar, o pacote é entregue sem a prova e o manifest.json não a lista, então os scripts offline e o verificador a informam como não presente, e não como ausente. Com `include_eidas` em uma chave live, o mesmo hash também recebe um carimbo do tempo qualificado eIDAS (RFC 3161, manifest.json.tsr). Capturas de tela em JPEG são assinadas com C2PA Content Credentials na base do melhor esforço: quando a assinatura falha, a captura é concluída sem elas. A pasta verification/ contém scripts que verificam o pacote offline. Todo manifest.json declara `ots_listed_only_when_present: true` (manifest.json.ots só é listado quando está no pacote) e `eidas_marker_version: 1`. Ele registra `eidas_requested: true` apenas quando o pacote é entregue com seu carimbo do tempo qualificado. Se não for possível obter o token, o manifesto é selado novamente sem ele. Um pacote que registra `eidas_requested: true` mas não tem manifest.json.tsr, portanto, perdeu seu token, e os scripts offline informam FAIL. O chain_of_custody.json registra `bitcoin_ots_included` e lista, em `page_modifications.items[]`, toda alteração feita na página antes da captura. O manifest.json traz sealed_at (quando foi montado, depois do hash de cada arquivo) e os desvios do relógio do servidor de captura medidos por SNTP; o chain_of_custody.json lista as etapas da captura na ordem em que ocorreram, cada uma com o horário em que terminou, e fecha com custody_record_written (o cálculo dos hashes, a assinatura e os carimbos do tempo vêm depois e são atestados pelo manifest.json e pelas provas).\n\nO metadata.json registra:\n\n- `browser_identity`: o user agent e os Client Hints efetivamente enviados.\n- `dns`: uma verificação de DNS independente do endereço ao qual o navegador se conectou, com as respostas de dns.google e cloudflare-dns.com e o resultado: confirmed, not_confirmed, resolvers_unavailable, connected_ip_unknown, skipped_ip_literal (a URL indica um endereço IP, então não há nome de host a consultar) ou skipped_internal_host (um nome de host interno, que nunca é enviado aos resolvedores públicos).\n- `url_at_load` e `url_at_screenshot`: o endereço logo após o carregamento da página e no momento em que sua imagem foi feita (uma página pode alterar o próprio endereço, geralmente a parte depois do #).\n- `redirect_chain`: os redirecionamentos do documento principal, do mais antigo ao mais recente, cada um com seu status HTTP. Os endereços solicitado e final são informados por completo; em cada salto intermediário os valores da query string são substituídos por [redacted] (`query_values_redacted`).\n- `tls.source`: a origem de cada campo TLS, `browser_connection` ou `browser_connection_and_separate_handshake`, este último com `at`, `ip` e `port` do handshake separado que obteve o certificate_chain.pem após o carregamento da página.\n\nQuando uma etapa solicitada não foi executada (um carimbo do tempo eIDAS, a assinatura C2PA, a solicitação de arquivamento, o WHOIS, a segunda leitura do relógio, um WARC truncado pelo limite de tempo, ou uma espera ou expansão de conteúdo pulada por causa do limite de tempo), o evidence.pdf a lista com um motivo curto e o certificado de procedência a menciona em uma linha.\n\nAs capturas são assíncronas. POST /captures retorna 202 com um capture_id; consulte GET /captures/{capture_id} periodicamente ou passe `options.webhook_url`. Uma captura concluída retorna um package_url pré-assinado válido por até 24 horas (veja package_url_expires_at); leia a captura novamente para obter um link novo.\n\nOs caminhos abaixo são relativos à URL do servidor, que já contém o estágio /v1. Os valores `poll_path` retornados pela API incluem /v1.",
    "contact": {
      "email": "support@getproofsnap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.getproofsnap.com/v1",
      "description": "ProofSnap API (UE)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Capturas",
      "description": "Criar, listar, ler, excluir e verificar capturas.\n\n**Limites de requisições.** `POST /captures`, `POST /captures/batch`, `POST /compare`, `POST /captures/{capture_id}/redact` e `POST /certify-hash` têm limite de requisições por conta em janelas fixas de 60 segundos: todas as chaves de API da conta e a respectiva sessão do painel compartilham uma janela (adicionar chaves não aumenta o limite). Um lote ou uma certificação de vários hashes usa uma unidade por URL ou hash. Limites padrão por janela, por plano:\n\n| Plano | Requisições por 60 s |\n| --- | --- |\n| sandbox | 10 |\n| developer | 60 |\n| startup | 120 |\n| growth | 300 |\n| custom | 1200 |\n| legal_solo | 60 |\n| legal_seats | 120 |\n| website_archive | 60 |\n| archive_only | 30 |\n| qualquer outro plano | 30 |\n\nAs requisições a seguir também consomem da mesma janela, uma unidade por requisição, salvo indicação em contrário: `POST /captures/{capture_id}/verify`, `PATCH /captures/{capture_id}` com a ação legal_hold, `GET /captures/hosts` (somente quando a lista é recalculada, não quando um resultado em cache é retornado), `POST /legal-holds` (uma unidade a cada 10 capturas), `POST /captures/export-jobs`, e `POST /imports`, `POST /imports/{import_id}/complete` e `GET /imports/{import_id}?upload=true`.\n\nQuando o limite é excedido, a API retorna 429 RATE_LIMITED com os cabeçalhos `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset` e `error.details.retry_after_seconds`. Uma requisição que precisa de mais unidades do que uma janela permite (uma certificação de vários hashes maior que o limite por janela) nunca pode ter sucesso e retorna 400 RATE_LIMIT_COST_TOO_HIGH com `error.details` `{ max_per_window, window_seconds, requested }`. Divida-a em requisições menores. Uma requisição recusada não consome unidades, e os cabeçalhos informam o saldo restante real.\n\n**Limites mensais.** Capturas, comparações, tarjamentos e certificações com chaves live contam para um máximo mensal por conta (mês civil UTC). A resposta de aceite traz cabeçalhos `X-Quota-*`. Ao atingir o máximo, a API retorna 402 QUOTA_EXCEEDED com `error.details` `{ reason, used, limit, max_limit, cap, org_member }`, em que `cap` é `plan_max`, `customer`, `member_owner` ou `org`. Enquanto uma assinatura live estiver em atraso ou não paga, requisições cobradas retornam 402 PAYMENT_REQUIRED; para essa conta, capturas (individuais e em lote) são recusadas com esse erro independentemente do ambiente da chave, inclusive chaves de teste.\n\n**Capturas em execução e modo de teste.** Cada conta só pode executar um número limitado de capturas ao mesmo tempo (developer 10, startup 25, growth 100, legal_solo e legal_seats 25, custom 200, website_archive 25, sandbox 5). Acima desse limite, a API retorna 429 CONCURRENT_CAPTURE_LIMIT; um lote inicia as URLs que cabem e lista as demais em `not_admitted` (cada uma com `code` CONCURRENT_CAPTURE_LIMIT, além de `in_flight_limit`), então envie-as novamente mais tarde. O trabalho em modo de teste (uma chave de teste ou uma sessão sem cobrança vinculada) é limitado a 200 unidades por conta por dia UTC (429 TEST_MODE_DAILY_LIMIT; cabeçalhos `X-Test-Daily-*`), e uma captura em modo de teste nunca é enviada ao Internet Archive (`external_archive.skip_reason` \"test_mode\")."
    },
    {
      "name": "Comparar e tarjar",
      "description": "Gera um novo pacote selado a partir de capturas existentes: uma comparação de duas capturas ou uma cópia tarjada de uma."
    },
    {
      "name": "Certificação de hash",
      "description": "Certifica hashes SHA-256 de arquivos que você guarda. Apenas os hashes são enviados; os arquivos nunca chegam ao ProofSnap."
    },
    {
      "name": "Capturas automáticas",
      "description": "Capturas recorrentes (interval ou cron) e monitoramento de alterações (on_change). Cada execução que faz uma captura é uma captura normal: é cobrada e conta para os limites mensais."
    },
    {
      "name": "Casos",
      "description": "Espaços de trabalho de casos jurídicos com um registro de custódia encadeado por hash. Disponível nos planos Legal (legal_solo, legal_seats); os demais planos recebem 403 MATTERS_NOT_ENABLED, com duas exceções. O proprietário de um escritório cuja assinatura terminou ainda pode listar, abrir e exportar os casos desse escritório até a data de exclusão dos dados do escritório (data_delete_after); esse acesso é somente leitura mais exportação, e GET /matters então adiciona read_only: true. Uma conta no plano Archive only pode listar, abrir e exportar seus casos. Apenas o proprietário do caso pode renomeá-lo, arquivá-lo, gerenciar membros, selá-lo, compartilhá-lo ou exportá-lo. O proprietário do caso é quem o criou. Em um caso do escritório, o proprietário do escritório só se torna proprietário do caso depois que o criador sai do escritório. Um proprietário de escritório que foi adicionado como membro tem apenas direitos de membro. Membros recebem 403 FORBIDDEN para essas operações. Toda escrita em um caso (criação, PATCH, atribuição de capturas, compartilhamento e sua revogação, selagem, exportação) exige um endereço de e-mail confirmado (403 EMAIL_NOT_VERIFIED, para uma sessão do painel e para uma chave de API de uma conta cujo endereço não foi confirmado) e, para um chamador live (chave de API live, ou uma sessão com cobrança vinculada), é recusada com 402 PAYMENT_REQUIRED enquanto a assinatura (ou a do escritório) estiver em atraso ou não paga. Um caso arquivado recusa renomeações, novos membros, novas capturas, compartilhamento e exportação (409 MATTER_ARCHIVED) até ser reaberto; um chamador que não pode reabri-lo (o proprietário de um escritório encerrado, uma conta Archive only) ainda pode exportá-lo."
    },
    {
      "name": "Webhooks",
      "description": "ProofSnap envia via POST um evento JSON para o `webhook_url` que você informa com uma captura, certificação, tarjamento ou agendamento. A URL deve usar https, não pode conter credenciais (`user:password@`, 400 INVALID_URL) e não pode apontar para um endereço privado, de loopback, link-local, de NAT de operadora (carrier-grade NAT), multicast, reservado ou de metadados de nuvem, incluindo formas IPv6 que embutem um desses endereços IPv4. Isso é verificado quando a requisição é aceita (422 SSRF_BLOCKED) e novamente em cada entrega: o hostname é resolvido, cada endereço é verificado e a entrega se conecta somente a um endereço aprovado na verificação, de modo que um host que depois passe a resolver para um endereço bloqueado não é contatado. Quando o hostname não pode ser resolvido, a entrega não é enviada. Em todas as rotas, um webhook_url vazio ou só com espaços em branco é tratado como ausência de webhook. /compare não envia webhooks.\n\n**Assinaturas da conta.** Em vez de (ou além de) um webhook_url por requisição, inscreva um endpoint nos eventos da conta com `POST /webhook-subscriptions`: todo evento correspondente da conta é enviado a ele, seja qual for a chave, o agendamento ou a sessão do painel que o causou. Cada assinatura tem seu próprio segredo (retornado uma única vez, na criação), lista os tipos de evento que deseja e pode filtrar por `host` ou `schedule_id`; uma assinatura live recebe eventos apenas de capturas live, e uma assinatura de teste recebe eventos apenas de capturas de teste. Uma chave de API lista e altera apenas as assinaturas do seu próprio ambiente; a sessão do painel vê todas. As entregas dela trazem `subscription_id` no nível superior e nenhum link pré-assinado (package_url, package_url_expires_at, upgraded_proof_url e compare.package_url são removidos; monitor.change_detected traz compare.compare_id): obtenha o pacote com GET /captures/{capture_id}. Eventos de captura também trazem `capture_id` no nível superior. O webhook-id de uma entrega de assinatura é derivado da assinatura, da captura (ou do agendamento) e do evento. Valem as regras de entrega e as novas tentativas descritas abaixo. Além disso, uma resposta 410 desativa a assinatura imediatamente (disabled_reason gone), entregas que falham por 5 dias sem nenhum sucesso (e com pelo menos um ciclo completo de novas tentativas falhas) a desativam (failing), e revogar a chave de API que a criou a desativa (key_revoked); PATCH status \"active\" a reativa. Todo evento também fica guardado por 30 dias em `GET /events`.\n\n**Cabeçalhos**\n\nToda entrega é assinada conforme a especificação [Standard Webhooks](https://www.standardwebhooks.com/), então qualquer biblioteca oficial Standard Webhooks pode verificá-la:\n\n| Cabeçalho | Valor |\n| --- | --- |\n| `Content-Type` | `application/json` |\n| `webhook-id` | `msg_<delivery id>`: o mesmo para o mesmo evento da mesma captura, então uma entrega duplicada traz o mesmo id. Use-o para deduplicar: processe cada webhook-id uma única vez. `GET /webhook-events/{capture_id}` retorna o mesmo id sem o prefixo `msg_` como `delivery_id` |\n| `webhook-timestamp` | Horário Unix, em segundos, em que a entrega foi enviada. É coberto pela assinatura |\n| `webhook-signature` | `v1,<base64>`: HMAC-SHA256 sobre `<webhook-id>.<webhook-timestamp>.<raw body>`. Durante uma rotação do segredo do webhook, traz duas entradas separadas por espaço, `v1,<new> v1,<previous>`; aceite a entrega quando qualquer entrada corresponder |\n| `User-Agent` | `ProofSnap-Webhook/2.0` |\n\n**Verificar uma entrega.** Verifique sobre os bytes exatos que você recebeu, antes de fazer o parsing. Rejeite uma entrega cujo webhook-timestamp difira do seu relógio em mais de 5 minutos (as bibliotecas oficiais fazem isso) e deduplique pelo webhook-id. A chave de assinatura é a parte do segredo depois de `whsec_`, decodificada de base64. `webhook_secret` é `whsec_` seguido de base64 padrão, a forma que as bibliotecas oficiais esperam, então passe-o a elas como está.\n\n```js\n// npm install standardwebhooks\nimport { Webhook } from \"standardwebhooks\";\nconst wh = new Webhook(process.env.PROOFSNAP_WEBHOOK_SECRET); // whsec_... (webhook_secret)\n// rawBody: the request body as received (string or Buffer), headers: the request headers\nconst event = wh.verify(rawBody, headers); // throws on a bad signature or a timestamp outside 5 minutes\n```\n\nSem biblioteca (Node):\n\n```js\nconst key = Buffer.from(secret.slice(\"whsec_\".length), \"base64\");\nconst signed = `${headers[\"webhook-id\"]}.${headers[\"webhook-timestamp\"]}.${rawBody}`;\nconst expected = crypto.createHmac(\"sha256\", key).update(signed).digest(\"base64\");\nconst ok = headers[\"webhook-signature\"].split(\" \").some((part) => {\n  const [version, sig] = part.split(\",\");\n  return version === \"v1\" && sig && sig.length === expected.length\n    && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n});\n// and: Math.abs(Date.now() / 1000 - Number(headers[\"webhook-timestamp\"])) <= 300\n```\n\n**Segredo do webhook.** Cada chave de API tem seu próprio segredo (`whsec_...`). Ele é exibido uma única vez, quando você cria a chave no painel em Desenvolvedores (o campo `webhook_secret` da resposta que cria a chave). Ele não pode ser lido depois: a listagem de chaves nunca o retorna. Se você o perder, ou se ele puder ter vazado, gere um novo no painel em Desenvolvedores (Novo segredo de webhook na chave). O novo segredo também é exibido uma única vez. **Sobreposição na rotação:** por 24 horas após uma rotação (até `previous_secret_expires_at` na resposta da rotação), toda entrega é assinada tanto com o segredo novo quanto com o anterior (duas entradas `v1,` em webhook-signature), para que você possa trocar seu endpoint para o novo segredo sem perder entregas. Uma segunda rotação enquanto essa sobreposição ainda está em andamento é recusada com 409 WEBHOOK_SECRET_ROTATION_PENDING e `error.details.previous_secret_expires_at` (quando você poderá rotacionar de novo), de modo que nunca há mais de dois segredos ativos ao mesmo tempo e um segredo em que os receptores ainda possam confiar nunca é aposentado antes da hora. Para um segredo que vazou, revogue a chave e crie uma nova. Revogar a chave apaga os dois. As entregas usam os segredos da chave no momento em que são enviadas, incluindo entregas de trabalhos iniciados antes de uma rotação. Uma requisição de uma sessão autenticada do painel não tem chave de API e, portanto, nenhum segredo de webhook, então um `webhook_url` nela é recusado com 400 `WEBHOOK_REQUIRES_API_KEY` (capturas, lotes, tarjamentos, certificações de hash, criação ou edição de uma captura agendada). Execuções agendadas usam o segredo da chave que criou o agendamento. Quando a chave foi revogada, ou seu segredo não pode ser lido no momento da entrega, o evento não é enviado e a tentativa é registrada com o motivo (consulte com `GET /webhook-events/{capture_id}`).\n\n**Entrega.** Cada tentativa tem timeout de 10 segundos. Redirecionamentos nunca são seguidos: uma resposta 3xx conta como falha, assim como uma resposta fora de 2xx ou um timeout. O corpo da resposta não é lido. **Novas tentativas:** uma entrega que excede o timeout, não consegue se conectar ou recebe uma resposta fora de 2xx (exceto 410 Gone) é enviada novamente cerca de 5 minutos, 30 minutos, 2 horas, 8 horas e 24 horas depois (tentativas 2 a 6, cerca de 35 horas no total), sempre com o mesmo `webhook-id` e um novo `webhook-timestamp`. Depois disso, a entrega é abandonada. Uma entrega repetida traz o mesmo payload SEM o link de download de 24 horas (`package_url`, `package_url_expires_at`): obtenha o pacote com GET /captures/{capture_id}. Entregas de uma captura apagada nesse meio-tempo são descartadas. Uma falha transitória ao ler o segredo de assinatura do nosso lado é tentada novamente da mesma forma. Um 410 Gone, um redirecionamento, um endereço bloqueado ou uma chave ausente ou revogada não geram nova tentativa. O corpo de uma nova tentativa pode diferir da primeira tentativa (sem link de download): deduplique pelo webhook-id, não por um hash do corpo. Responda com 2xx rapidamente. O log de tentativas registra uma classe de erro genérica (connection_failed, timeout, blocked_address, redirect_not_followed, http_<status>), nunca o erro de rede bruto. certify.completed e redaction.completed são enviados de forma assíncrona, pouco depois de a resposta ser retornada. Toda tentativa é registrada; consulte o log com `GET /webhook-events/{capture_id}`.\n\n**Eventos**\n\n`capture.completed`: uma captura (avulsa, item de lote ou execução de agendamento por intervalo/cron) foi concluída. As assinaturas de webhook da conta também o recebem para as capturas de referência e de mudança de um agendamento on_change (o webhook_url do próprio agendamento recebe monitor.change_detected para elas). `capture` tem os mesmos campos de um `GET /captures/{capture_id}` concluído, mais `timestamps.eidas` quando um carimbo do tempo qualificado foi adicionado, `schedule_id` (null fora de um agendamento), `test_mode` e `completed_at`.\n\n```json\n{ \"event\": \"capture.completed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"completed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"captured_at\": \"...\", \"completed_at\": \"...\",\n    \"package_url\": \"https://...\", \"package_url_expires_at\": \"...\", \"verification_url\": \"https://...\",\n    \"manifest_hash\": \"sha256:...\", \"timestamps\": { \"bitcoin_ots\": { \"status\": \"pending_confirmation\", \"block_height\": null } },\n    \"c2pa\": { \"included\": true, \"signed_screenshot_sha256\": \"sha256:...\" }, \"file_count\": 21, \"size_bytes\": 1234567, \"metadata\": {} } }\n```\n\n`capture.failed`: a captura não pôde ser concluída. `error.message` é um motivo público curto (para uma falha inesperada, \"The capture could not be completed.\"); detalhes internos do erro nunca são enviados. Isso inclui uma execução que travou ou excedeu o tempo: ela é marcada como falha por uma varredura cerca de 30 minutos depois de iniciada, e capture.failed é enviado nesse momento (mensagem \"Capture did not finish (timed out).\"). A mesma varredura envia certify.failed ou redaction.failed para uma certificação de hash ou um tarjamento que não terminou. Uma execução que termina depois de ter sido informada como falha não é cobrada e não envia capture.completed.\n\n```json\n{ \"event\": \"capture.failed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"failed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CAPTURE_FAILED\", \"message\": \"...\" }, \"metadata\": {} } }\n```\n\n`certify.completed`: uma certificação de hash foi concluída. `certificate` tem os mesmos campos da resposta síncrona de `POST /certify-hash`. Entregue de forma assíncrona, pouco depois de a resposta ser retornada.\n\n`certify.failed`: uma certificação de hash não pôde ser concluída.\n\n```json\n{ \"event\": \"certify.failed\",\n  \"certificate\": { \"certify_id\": \"cert_...\", \"status\": \"failed\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CERTIFY_FAILED\", \"message\": \"The certification could not be completed. Retry the request.\" } } }\n```\n\n`redaction.completed`: um tarjamento foi concluído. `redaction` tem os mesmos campos da resposta de `POST /captures/{capture_id}/redact`.\n\n`redaction.failed`: um tarjamento não pôde ser concluído. `error.code` é o código que a requisição teria retornado (por exemplo REDACTION_LEAK_DETECTED), com `details` quando houver; um tarjamento que a varredura marcou como falho traz REDACTION_FAILED.\n\n```json\n{ \"event\": \"redaction.failed\",\n  \"redaction\": { \"redaction_id\": \"red_...\", \"status\": \"failed\", \"redacted_from\": \"cap_...\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"REDACTION_LEAK_DETECTED\", \"message\": \"...\", \"details\": { \"files\": [\"page.html\"], \"rule_indexes\": [0], \"metadata_fields\": [], \"in_capture_url\": false } } } }\n```\n\n`monitor.change_detected`: um agendamento `on_change` detectou mudança no texto da página e fez uma nova captura.\n\n```json\n{ \"event\": \"monitor.change_detected\", \"schedule_id\": \"sch_...\",\n  \"before_capture_id\": \"cap_...\", \"after_capture_id\": \"cap_...\", \"url\": \"https://example.com\",\n  \"compare\": { \"package_url\": \"https://...\", \"diff_summary\": { } },\n  \"diff_status\": \"ok\", \"verdict\": \"changed\", \"text_verdict\": \"changed\", \"images_verdict\": \"unchanged\", \"metadata_verdict\": \"unchanged\",\n  \"detected_at\": \"...\", \"test_mode\": false }\n```\n\n`verdict`, `text_verdict`, `images_verdict` e `metadata_verdict` (desde 2026-10-14) refletem os resultados por categoria da comparação em compare.diff_summary (null sem comparação). Um agendamento com `alert_on: \"text\"` envia monitor.change_detected somente quando text_verdict é changed (ou quando não há resultado de comparação).\n\n`capture.anchor_confirmed` (somente assinaturas e GET /events): a ancoragem Bitcoin da prova OpenTimestamps do pacote foi confirmada, normalmente algumas horas após a captura.\n\n```json\n{ \"event\": \"capture.anchor_confirmed\", \"capture_id\": \"cap_...\", \"schedule_id\": null, \"url\": \"https://example.com\",\n  \"confirmed_at\": \"...\", \"bitcoin_block_height\": 968549, \"upgraded_proof_available\": true, \"test_mode\": false }\n```\n\n`schedule.paused` (somente assinaturas e GET /events): ProofSnap pausou um agendamento. `reason` é `no_billing` (sem acesso à API), `plan_downgrade` (o plano permite menos agendamentos, não permite on_change ou não permite novos trabalhos), `failures` (cinco execuções falhas seguidas), `key_revoked` ou `invalid_cron`. Uma pausa feita por você mesmo não é um evento.\n\n```json\n{ \"event\": \"schedule.paused\", \"schedule_id\": \"sch_...\", \"url\": \"https://example.com\", \"reason\": \"failures\",\n  \"paused_at\": \"...\", \"test_mode\": false }\n```\n\n`diff_status` é `ok` (compare contém a comparação), `no_baseline` (nenhuma captura anterior para comparar) ou `failed` (a comparação falhou; compare é null)."
    },
    {
      "name": "Público",
      "description": "Operações que não exigem chave de API."
    },
    {
      "name": "Importação de pacotes",
      "description": "Importe pacotes de provas ProofSnap existentes (arquivos ZIP gerados pela extensão ProofSnap: capturas web, sessões com várias abas, certificações de arquivo individuais e combinadas; ou por esta API: capturas, certificações, comparações, tarjamentos) para a sua conta, para que sejam tratados como as suas capturas da API: mantidos por 10 anos e disponíveis para retenção legal, casos e exportações.\n\nSomente planos Legal (legal_solo, legal_seats), com uma chave de API live ou uma sessão do painel com faturamento vinculado (uma chave de teste, uma sessão sem faturamento e o sandbox recebem 403 IMPORT_NOT_ALLOWED). Um endereço de e-mail não confirmado recebe 403 EMAIL_NOT_VERIFIED, uma assinatura em atraso recebe 402 PAYMENT_REQUIRED. Uma importação não é uma captura: não conta para os limites mensais e não tem tarifa por importação; é cobrada apenas como dados armazenados.\n\n**Fluxo.** 1. `POST /imports` com o nome do arquivo e o tamanho exato em bytes (no máximo 2 GiB): a resposta contém uma URL pré-assinada por parte de 32 MiB (a última parte é menor), válida por 1 hora. 2. Faça PUT dos bytes de cada parte na URL correspondente e guarde o cabeçalho de resposta `ETag` de cada uma. 3. `POST /imports/{import_id}/complete` com cada número de parte e o respectivo ETag. 4. Consulte `GET /imports/{import_id}` até que o status seja final.\n\n**Status.** `uploading` (aguardando os uploads das partes e a chamada complete; um upload não concluído em 24 horas passa a `expired`), `verifying` (o pacote está sendo verificado), `accepted` (a verificação passou: `capture_id` é a nova captura, o mesmo id da importação), `rejected` (a verificação não passou: `reasons` lista os códigos; nada é mantido além do registro da importação), `duplicate` (a sua conta já tem uma captura com o mesmo manifesto: `duplicate_of`), `expired`.\n\n**Verificação.** Estrita: a assinatura RSA sobre o manifest.json, cada arquivo listado presente com o SHA-256 listado (e o SHA-512 e o tamanho, quando listados), nenhum arquivo que o manifesto não liste (exceto arquivos que o sistema operacional adiciona, como .DS_Store, e as provas derivadas do manifesto), pelo menos um arquivo com hash, um evidence id consistente, a cadeia de hashes do log forense recalculada, a prova OpenTimestamps contra o manifesto (offline) e o carimbo do tempo qualificado eIDAS contra a raiz Disig fixada, com os dados de validação contidos no pacote. A estrutura do ZIP é verificada antes de qualquer leitura (sem criptografia, sem ZIP64, sem links, nomes seguros, sem nomes duplicados, tamanhos e CRC-32 de cada entrada). Um pacote de teste da API (com marca d'água) é rejeitado. Pacotes da API selados antes de o evidence.pdf passar a ter hash são aceitos com esse arquivo listado em `verification.checks.files.unhashed`.\n\n**Níveis** (`summary`): `integrity` (verified ou failed), `time_anchor` (eidas_qualified, eidas_qualified_revocation_unknown, ots_bitcoin_attested_unconfirmed, ots_pending, none) e `provenance`: para pacotes da API, `issued_by_proofsnap` (o manifesto está no registro de pacotes emitidos da API) ou `unverified`; para pacotes da extensão, `registered_by_extension_account` (um usuário conectado da extensão registrou esse manifesto quando ele foi gerado) ou `not_registered`; `unknown` quando a consulta não pôde ser feita. A proveniência nunca faz uma importação falhar.\n\n**A captura.** Uma importação aceita é uma captura com `source` \"import\": pode ser baixada (o ZIP enviado, byte a byte) e verificada (POST /captures/{capture_id}/verify executa as mesmas verificações de novo), pode ser colocada sob retenção legal, movida para a lixeira e apagada, aparece em casos (quando importada com `matter_id`, o registro de custódia do caso registra `package.imported`) e em exportações, e é mantida por 10 anos a partir da importação. O seu `captured_at` é o momento que o próprio pacote declara (`import.captured_at_basis` \"package_claim\"), nunca um momento observado pelo ProofSnap; `created_at` é quando o upload foi criado. Comparações e tarjamentos de um pacote importado são recusados (422 IMPORT_NOT_SUPPORTED).\n\n**Limites.** No máximo 3 importações em andamento (uploading ou verifying) por conta (409 IMPORT_LIMIT), 50 importações criadas por conta e por dia UTC (429 RATE_LIMITED, `details.action` \"import_create\"; toda requisição de criação conta, inclusive uma que depois é rejeitada) e o limite de requisições por minuto do plano em create e complete. Volume de importação: no máximo 5 GiB por dia UTC e 20 GiB por mês UTC de tamanhos de pacote anunciados por conta, contados no create (e no complete, para um upload mais antigo); acima disso, 429 IMPORT_DAILY_BYTES_LIMIT ou IMPORT_MONTHLY_BYTES_LIMIT com `Retry-After`; um upload cancelado não conta."
    }
  ],
  "paths": {
    "/captures": {
      "post": {
        "tags": [
          "Capturas"
        ],
        "operationId": "createCapture",
        "summary": "Capturar uma URL",
        "description": "Aceita a captura e retorna 202 imediatamente; a captura é executada em segundo plano. Consulte `GET /captures/{capture_id}` periodicamente ou informe `options.webhook_url`. O hostname da URL é resolvido e verificado antes de a captura ser aceita.\n\nLimite de requisições por conta em janelas de 60 segundos (todas as chaves de API da conta e a respectiva sessão do painel compartilham uma janela). Depois que o limitador é executado, as respostas desta rota incluem os cabeçalhos `RateLimit-Policy` e `RateLimit` (baseados no IETF draft-ietf-httpapi-ratelimit-headers), além dos legados `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Uma resposta 429 adiciona `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceita. Uma repetição idempotente de uma requisição anterior com o mesmo Idempotency-Key também retorna 202, com `Idempotent-Replay: true` e o corpo original.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureAccepted"
                },
                "example": {
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "status": "processing",
                  "url": "https://example.com",
                  "poll_path": "/v1/captures/cap_a1B2c3D4e5F6g7H8",
                  "message": "Capture accepted. Poll GET /v1/captures/{capture_id} (or use webhook_url) for completion.",
                  "metadata": {}
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, MISSING_FIELD, INVALID_URL, INVALID_TIMESTAMP_TYPE, INVALID_VIEWPORT, METADATA_TOO_LARGE ou INVALID_EVIDENCE_PACK_OPTIONS. WEBHOOK_REQUIRES_API_KEY: webhook_url enviado a partir de uma sessão do painel (webhooks exigem uma chave de API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED (máximo mensal ou franquia do sandbox; traz os cabeçalhos X-Quota-* ou X-Sandbox-*) ou PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas com uma chave de teste (uma chave ps_live_ de uma conta sem conta de cobrança vinculada funciona como chave de teste). EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria nenhum trabalho novo. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o painel). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel ProofSnap (trabalho novo nesse plano é criado apenas no painel; uma barreira leve, não uma fronteira de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED, TARGET_UNREACHABLE ou IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete Retry-After. RateLimit informa r=0 e os segundos até a janela reiniciar. Também RATE_LIMITED, para uma sessão do painel Website Archive: seu teto diário de 100 unidades por dia UTC, compartilhado entre capturas únicas e em lote (uma unidade por URL), compare, redact e certify-hash (uma unidade por requisição); uma requisição que não cabe é recusada por inteiro (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After e details.retry_after_seconds dão os segundos até o próximo dia UTC, e os cabeçalhos RateLimit e X-RateLimit-* por minuto são omitidos). Uma recusa curta apenas com details.retry_after_seconds e Retry-After: 1 indica uma atualização concorrente do contador: tente novamente. Uma recusa que não é culpa do chamador devolve as unidades; um trabalho aceito com 202 que falha depois mantém sua unidade. Uma barreira leve, não uma fronteira de segurança. Uma captura única cujo Idempotency-Key é atendido a partir do armazenamento (uma repetição, 409 ou 422) é atendida antes do teto diário e não consome unidade.\n\nCONCURRENT_CAPTURE_LIMIT: capturas demais da conta estão em execução ao mesmo tempo (developer 10, startup 25, growth 100, legal_solo e legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` e Retry-After indicam quando tentar novamente.\n\nTEST_MODE_DAILY_LIMIT: o trabalho em modo de teste (uma chave de teste, ou uma sessão sem cobrança vinculada) é limitado a 200 unidades por conta e dia UTC; `details.used`, `details.limit`, `details.reset_at`, os cabeçalhos `X-Test-Daily-*` e Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CAPTURE_FAILED (não foi possível iniciar a captura; details.capture_id) ou INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Capturas"
        ],
        "operationId": "listCaptures",
        "summary": "Listar suas capturas",
        "description": "Linhas que você criou, das mais recentes para as mais antigas, em todos os status (incluindo as excluídas, a lixeira; filtre com status). Cada linha traz source, redacted_from, label e compared, de modo que o seu tipo pode ser exibido sem lê-la novamente. Uma captura feita para um escritório só é listada enquanto você ainda puder agir sobre ela (você ocupa um assento desse escritório ou é o proprietário dele); por isso uma página pode conter menos linhas do que limit, e next_cursor continua após a última linha lida. Certificados, comparações e tarjamentos estão incluídos.\n\nConsulta (somente a própria lista): url, host ou schedule_id, combinados com from / to e status, encontram o registro de uma página em um determinado dia. Um filtro inválido, dois entre url / host / schedule_id, ou from não anterior a to resultam em 400 MALFORMED_REQUEST.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tamanho da página, de 1 a 200 (padrão 50). Um valor fora do intervalo resulta em 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor da página anterior (formato created_at|capture_id, ou completed_at|capture_id com order=completed_at). Passe-o sem alteração.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Somente linhas neste status: processing, completed, failed ou deleted (lixeira). Qualquer outro valor resulta em 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Somente capturas desta URL exata, como foi capturada (http ou https). No máximo um entre url, host e schedule_id.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "Apenas capturas cuja URL está neste nome de host (sem diferenciar maiúsculas e minúsculas, por exemplo www.example.com; subdomínios são hosts distintos). No máximo um entre url, host e schedule_id. Registros criados antes de 2026-10-05 trazem o host depois que o backfill foi executado.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "query",
            "required": false,
            "description": "Somente capturas feitas por este agendamento. No máximo um entre url, host e schedule_id.",
            "schema": {
              "type": "string",
              "pattern": "^sch_[A-Za-z0-9]{8,32}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Criado neste momento ou depois: uma data (YYYY-MM-DD, meia-noite UTC) ou um timestamp ISO 8601.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Criado antes deste momento (exclusivo): uma data (YYYY-MM-DD, meia-noite UTC) ou um timestamp ISO 8601. Deve ser posterior a from. Com schedule_id ou url, a linha mais recente antes de um momento é a versão que estava vigente naquele momento.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "created_at (padrão: as criadas mais recentemente primeiro) ou completed_at (as concluídas mais recentemente primeiro; somente com status=completed, caso contrário 400 MALFORMED_REQUEST). Com completed_at, from e to delimitam completed_at, e next_cursor tem o formato completed_at|capture_id. Funciona com url, host e schedule_id.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "completed_at"
              ],
              "default": "created_at"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Uma página de capturas.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "captures": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "completed_at": {
                            "type": "string",
                            "nullable": true,
                            "description": "Quando a captura foi concluída (null durante o processamento ou após uma falha)."
                          },
                          "batch_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "schedule_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "test_mode": {
                            "type": "boolean",
                            "nullable": true
                          },
                          "external_archive": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/ExternalArchive"
                              }
                            ],
                            "description": "Somente linhas completed. Igual a external_archive de GET /captures/{capture_id}."
                          },
                          "label": {
                            "type": "string",
                            "nullable": true,
                            "description": "O rótulo da linha (certificados: o rótulo informado; tarjamentos: qual captura eles tarjam)."
                          },
                          "compared": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            },
                            "description": "Somente comparações: os IDs das duas capturas de origem."
                          },
                          "source": {
                            "type": "string",
                            "nullable": true,
                            "description": "Como a linha foi criada: api (uma captura web), schedule, certify-hash (um certificado), compare (uma comparação) ou redact (um tarjamento); null em linhas mais antigas que o campo."
                          },
                          "redacted_from": {
                            "type": "string",
                            "nullable": true,
                            "description": "Somente tarjamentos: a captura a partir da qual o tarjamento foi feito."
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Quando a página foi capturada (linhas completed). Para um tarjamento, o momento da captura de origem (redacted_at é quando o tarjamento foi feito)."
                          },
                          "redacted_at": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Somente tarjamentos: quando o tarjamento foi feito. O captured_at de um tarjamento é o momento da captura de origem."
                          },
                          "compared_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Somente comparações: quando a comparação foi feita. Seu captured_at traz o mesmo valor (mantido para leitores mais antigos); use compared_at."
                          },
                          "manifest_hash": {
                            "type": "string",
                            "nullable": true,
                            "description": "sha256:<hex> do manifest.json do pacote (linhas completed)."
                          },
                          "private_note": {
                            "type": "string",
                            "nullable": true,
                            "description": "Sua nota privada sobre a captura (veja PrivateNote), nunca faz parte do pacote de provas."
                          },
                          "legal_hold": {
                            "type": "boolean",
                            "description": "A captura está sob retenção legal (não é excluída enquanto for true; liberada automaticamente quando a assinatura terminou e se passaram os 30 dias dela). Veja PATCH /captures/{capture_id}."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "null quando esta página tem menos linhas que limit.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: cursor, status (um de processing, completed, failed, deleted), limit (1 a 200) ou filtro de consulta (url, host, schedule_id, from, to; no máximo um de url / host / schedule_id; from anterior a to) inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/batch": {
      "post": {
        "tags": [
          "Capturas"
        ],
        "operationId": "createBatch",
        "summary": "Capturar várias URLs",
        "description": "Inicia uma captura por URL com as mesmas opções. Cada captura é independente: consulte cada capture_id ou receba um webhook por captura. Usa uma unidade do limite de requisições e uma unidade do limite mensal por URL. Um lote com mais URLs do que uma janela do limite de requisições permite nunca pode ter sucesso, por isso é recusado com 400 RATE_LIMIT_COST_TOO_HIGH em vez de 429. Divida-o em requisições menores. Uma repetição de um lote concluído (mesmo Idempotency-Key e mesmo corpo) é atendida antes de o limite de requisições ser aplicado e não consome unidade do limite.\n\nLimite de requisições por conta em janelas de 60 segundos (todas as chaves de API da conta e a sua sessão do painel compartilham uma janela). Depois que o limitador é executado, as respostas desta rota incluem os cabeçalhos `RateLimit-Policy` e `RateLimit` (baseados no IETF draft-ietf-httpapi-ratelimit-headers) mais os legados `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Um 429 adiciona `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "urls": [
                  "https://example.com/a",
                  "https://example.com/b"
                ],
                "options": {
                  "case_reference": "Smith v. Jones"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito. Uma repetição idempotente de um lote concluído com o mesmo Idempotency-Key e o mesmo corpo também retorna 202, com `Idempotent-Replay: true`; ela é atendida antes de o limite de requisições ser aplicado, então não consome nenhuma unidade do limite de requisições.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_BATCH (details: reason e received, ou index, url e um objeto reason aninhado para a URL inválida), MALFORMED_REQUEST ou um erro de opções. WEBHOOK_REQUIRES_API_KEY: webhook_url enviado a partir de uma sessão do painel (webhooks exigem uma chave de API). RATE_LIMIT_COST_TOO_HIGH: mais URLs do que uma janela do limite de requisições permite; details { reason, max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED ou PAYMENT_REQUIRED. Um lote também é recusado com QUOTA_EXCEEDED quando o uso mensal não pode ser lido naquele momento; tente novamente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas com uma chave de teste. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria novos trabalhos. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o painel). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel do ProofSnap (novos trabalhos nesse plano são criados apenas no painel; uma barreira leve, não uma fronteira de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete Retry-After. RateLimit informa r=0 e os segundos até a janela reiniciar. Também RATE_LIMITED, para uma sessão do painel Website Archive: seu teto diário de 100 unidades por dia UTC, compartilhado por capturas avulsas e em lote (uma unidade por URL), compare, redact e certify-hash (uma unidade por requisição); uma requisição que não cabe é recusada inteira (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After e details.retry_after_seconds dão os segundos até o próximo dia UTC, e os cabeçalhos por minuto RateLimit e X-RateLimit-* são omitidos). Uma recusa curta com apenas details.retry_after_seconds e Retry-After: 1 significa uma atualização concorrente do contador: tente novamente. Uma recusa que não é culpa de quem chama devolve as unidades; trabalho aceito com 202 que falha depois mantém a sua unidade. Uma barreira leve, não uma fronteira de segurança.\n\nCONCURRENT_CAPTURE_LIMIT: nenhuma URL do lote pôde ser iniciada porque a conta já tem tantas capturas em execução quanto lhe é permitido (developer 10, startup 25, growth 100, legal_solo e legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` e Retry-After. Quando apenas algumas cabem, o lote é aceito com 202 e lista as demais em `not_admitted`.\n\nTEST_MODE_DAILY_LIMIT: trabalho em modo de teste (uma chave de teste ou uma sessão sem faturamento vinculado) é limitado a 200 unidades por conta e dia UTC, uma por URL; `details.used`, `details.limit`, `details.reset_at`, os cabeçalhos `X-Test-Daily-*` e Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      }
    },
    "/captures/hosts": {
      "get": {
        "tags": [
          "Capturas"
        ],
        "operationId": "listCaptureHosts",
        "summary": "Listar os hostnames que você capturou",
        "description": "Todos os hostnames da sua própria lista de capturas, com o número de capturas e o horário da captura mais recente por host, para um filtro de domínio: passe um host de volta como GET /captures?host=. count é o número de linhas que GET /captures?host=<host> retorna sem status (entre as capturas lidas): capturas de teste e de produção igualmente, todos os status, incluindo as que estão na lixeira (status deleted), como a lista as mostra; capturas apagadas definitivamente e linhas sem URL de página (certificados) ficam de fora. Uma captura conta assim que traz o seu hostname armazenado (capturas criadas antes de 2026-10-05 o trazem depois que o backfill foi executado). Uma captura do escritório conta somente enquanto você ainda puder agir sobre ela (você ocupa um assento desse escritório ou é o proprietário dele). host está em minúsculas. Ordenado por count (maior primeiro) e depois por host (A a Z). No máximo 1000 hosts. As 5000 capturas mais recentes são lidas; truncated é true quando há mais de 1000 hosts ou capturas mais antigas que essas 5000, de modo que um host capturado apenas antes delas pode estar ausente. A lista é calculada no máximo uma vez por conta a cada 30 segundos e pode ter até 30 segundos de defasagem (um pouco mais enquanto outra requisição da conta a está calculando). Calculá-la consome uma unidade do seu limite de requisições por minuto; uma resposta servida a partir desse cache não custa nada. Disponível em todos os planos, para chaves de API e para a sessão do painel.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Os nomes de host.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hosts",
                    "truncated"
                  ],
                  "properties": {
                    "hosts": {
                      "type": "array",
                      "maxItems": 1000,
                      "items": {
                        "type": "object",
                        "required": [
                          "host",
                          "count",
                          "last_captured_at"
                        ],
                        "properties": {
                          "host": {
                            "type": "string",
                            "example": "www.example.com",
                            "description": "Nome de host em minúsculas, o valor que GET /captures?host= aceita."
                          },
                          "count": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 12,
                            "description": "Capturas deste host entre as capturas lidas: as linhas que GET /captures?host= retorna sem status, incluindo as que estão na lixeira."
                          },
                          "last_captured_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "example": "2026-10-05T07:48:38Z",
                            "description": "Horário da captura mais recente deste host (quando a página foi capturada; caso contrário, quando a captura foi solicitada), em UTC, com precisão de segundos."
                          }
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true quando a lista pode estar incompleta: mais de 1000 hosts, ou mais capturas do que as 5000 mais recentes que foram lidas."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: scope não é suportado (somente capturas próprias).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "RATE_LIMITED: o limite de requisições por minuto foi esgotado (calcular a lista custa uma unidade), ou a lista da sua conta está sendo calculada por outra requisição e não há resultado anterior armazenado; tente novamente após Retry-After segundos.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Capturas"
        ],
        "operationId": "getCapture",
        "summary": "Obter uma captura",
        "description": "Status e, depois de concluída, uma package_url nova válida por até 24 horas (veja package_url_expires_at). Funciona para IDs de captura, certificado, comparação e tarjamento. Pode ser lido pela conta que o criou, pelos membros do caso ao qual pertence e pelo proprietário do escritório para o qual foi feito (se a assinatura do escritório tiver terminado, até que a sua janela de download se encerre).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Envie download quando estiver baixando o pacote: para uma captura em um caso, isso registra capture.downloaded no log de acesso do caso. Consultas simples de status devem omiti-lo.",
            "schema": {
              "type": "string",
              "enum": [
                "download"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A captura.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a captura não é sua.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: ID desconhecido ou malformado, captura excluída, ou o arquivo do pacote não foi encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Capturas"
        ],
        "operationId": "deleteCapture",
        "summary": "Excluir uma captura",
        "description": "Padrão: move a captura para a lixeira. Ela pode ser restaurada com PATCH até restorable_until (30 dias); depois disso é eliminada definitivamente. Com permanent=true o pacote armazenado é apagado agora e o registro é reduzido a uma lápide (tombstone); isso não pode ser desfeito. Nenhuma das opções remove pacotes que você já baixou nem a prova OpenTimestamps. Uma captura em um caso aberto ou arquivado é primeiro removida do caso (registrado no seu log de custódia) quando quem chama pode abrir esse caso; quem não pode (seu autor depois de ter sido removido do caso) é recusado com 409 MATTER_LINKED, tanto para a lixeira quanto para permanent=true, até que o proprietário do caso a remova do caso; uma captura em um caso selado não pode ser excluída. Uma captura que ainda está em processamento não pode ser excluída (409 CAPTURE_PROCESSING); exclua-a quando tiver sido concluída ou falhado. Excluir uma captura que nunca foi concluída devolve a sua admissão mensal.\n\nCarência após liberação da retenção legal: por 14 dias após a liberação de uma retenção legal (`deletion_blocked_until` no GET) a captura não é excluída permanentemente (409 CAPTURE_HOLD_GRACE com `details.until`), nem por uma pessoa, nem pela eliminação da lixeira ou pela retenção (a exclusão da conta e do escritório após o fim da assinatura não espera por ela). Mover para a lixeira é permitido; a eliminação da lixeira aguarda o fim da carência. Uma exclusão permanente primeiro remove qualquer retenção legal do S3 Object Lock que ainda reste nos objetos armazenados; quando o armazenamento recusa, a requisição retorna 409 CAPTURE_LEGAL_HOLD (\"still locked in storage\", tente novamente mais tarde) e nada é excluído. Cada envio para a lixeira, restauração e exclusão permanente é registrado na cadeia de eventos de captura da conta (GET /captures/events). Um tarjamento ou comparação cuja captura de origem está sob retenção legal retorna `source_under_legal_hold: true` quando é enviado para a lixeira ou excluído.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "permanent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Excluída. Lixeira: { capture_id, status: \"deleted\", restorable: true, restorable_until }. Permanente: { capture_id, status: \"deleted\", permanent: true }. Já na lixeira: { capture_id, status }.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restorable": {
                      "type": "boolean"
                    },
                    "restorable_until": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "permanent": {
                      "type": "boolean"
                    },
                    "source_under_legal_hold": {
                      "type": "boolean",
                      "description": "Presente (true) somente quando se trata de um tarjamento ou comparação feito a partir de uma captura que está sob retenção legal."
                    },
                    "deletion_blocked_until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Lixeira durante a carência da liberação: a limpeza da lixeira aguarda até este momento."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a captura não é sua. FORBIDDEN também quando a captura pertence a um caso que não está selado e quem fez a chamada (o proprietário do escritório agindo sobre a captura de um assento, não o seu autor) não pode abrir esse caso. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o painel).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED: a captura é um anexo de um caso selado. MATTER_LINKED: a captura é um anexo de um caso do qual o chamador não faz mais parte; o proprietário do caso a remove primeiro. CAPTURE_PROCESSING: a captura ainda está em processamento (error.details.status). CAPTURE_LEGAL_HOLD: a captura está sob retenção legal e não é excluída (lixeira ou apagamento) por ninguém. CAPTURE_HOLD_GRACE: uma exclusão permanente dentro do período de carência após a liberação da retenção legal da captura (LEGAL_HOLD_RELEASE_GRACE_DAYS, 14 dias); error.details.until informa quando ela passa a ser possível. Mover para a lixeira é permitido durante a carência.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: não foi possível apagar o pacote armazenado; nada foi excluído.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Capturas"
        ],
        "operationId": "restoreCapture",
        "summary": "Restaurar uma captura excluída, definir a sua nota privada ou aplicar ou liberar a sua retenção legal",
        "description": "Restaura uma captura da lixeira. Se ela foi removida de um caso quando foi excluída, é adicionada de volta quando esse caso ainda está aberto e quem chama ainda pode abri-lo; matter_id e matter_relinked aparecem na resposta somente nesse caso (matter_id null quando a revinculação não ocorreu). Quem chama e não pode mais abrir esse caso recebe a captura de volta sem ele, nada é gravado no log de custódia do caso e a resposta não menciona o caso. Uma captura que não está na lixeira é retornada sem alteração.\n\nNota privada: um corpo com `private_note` (uma string de no máximo 200 caracteres, ou null para apagá-la) define, em vez disso, a nota privada da captura e não faz mais nada; não pode ser combinado com `action`. A nota é armazenada apenas no registro da captura, fora do pacote de provas selado, portanto alterá-la nunca modifica as provas nem seus hashes. Somente o autor da captura pode defini-la (o proprietário do escritório e os membros do caso recebem 403), em uma captura em qualquer estado, exceto uma excluída permanentemente (404). A resposta é { capture_id, status, private_note }.\n\nRetenção legal: um corpo `{ \"action\": \"legal_hold\", \"enabled\": true|false, \"reason\"?: string }` aplica (true) ou libera (false) a retenção legal da captura e não faz mais nada. Uma captura sob retenção legal não é excluída: DELETE (lixeira ou permanente) retorna 409 CAPTURE_LEGAL_HOLD para todos, e nem a eliminação da lixeira nem a exclusão por retenção a afetam; o GET mostra `storage.retention.earliest_deletion_at` null. Uma retenção é mantida somente enquanto a conta tem um plano que conserva dados (qualquer plano pago, incluindo Archive only; para uma captura do escritório: o escritório está ativo ou mantido como Company archive). Quando a assinatura termina, as capturas ficam disponíveis por 30 dias; se nenhum plano desse tipo for contratado até lá, todas as retenções legais da conta (ou do escritório) são liberadas automaticamente nessa data (ator system, motivo subscription_ended, registrado como qualquer liberação, sem carência de liberação) e as capturas são excluídas com as demais. Para manter capturas retidas, mude para Archive only (ou contrate o Company archive) antes dessa data, ou baixe-as. Nesses 30 dias nenhuma nova retenção pode ser aplicada (a conta não tem plano pago); as retenções existentes ainda podem ser liberadas. A retenção é armazenada apenas no registro da captura, fora do pacote de provas selado. `reason` é texto simples opcional de no máximo 200 caracteres (sem caracteres de controle, invisíveis ou de direção de texto; espaços em branco são agrupados, vazio significa nenhum); é mantido com a retenção e apagado quando a retenção é liberada.\n\nQuem: para aplicar uma retenção, qualquer pessoa que possa agir sobre a captura (seu autor, no caso de uma captura do escritório enquanto ainda for um assento desse escritório, e o proprietário do escritório, também de um escritório cuja assinatura terminou enquanto estiver dentro do período de retenção ou mantido como Company archive; o arquivo somente pela sessão do painel). Para liberá-la: em uma captura pessoal, seu autor; em uma captura do escritório, somente o proprietário do escritório (um assento recebe 403 LEGAL_HOLD_OWNER_ONLY). Aplicar uma retenção exige um plano pago (todo plano pago, incluindo Archive only): o sandbox gratuito, e também uma conta cuja assinatura terminou, recebe 403 LEGAL_HOLD_NOT_AVAILABLE com details.reason \"plan\" (um Company archive em vigor conta como pago para as capturas desse escritório, pela sessão do painel do seu proprietário), e uma captura em modo de teste (com marca d'água, feita com uma chave de teste ou sem faturamento vinculado) recebe 403 LEGAL_HOLD_NOT_AVAILABLE com details.reason \"test_capture\". Liberar uma retenção funciona em todos os planos, também depois do fim da assinatura. No Website Archive a chave de API é somente leitura (403 PLAN_API_READ_ONLY); a sessão do painel pode. Uma assinatura em atraso ou não paga não bloqueia isso. Uma sessão do painel ou chave cujo endereço de e-mail não foi confirmado recebe 403 EMAIL_NOT_VERIFIED.\n\nRecusado em uma captura na lixeira (409 CAPTURE_IN_TRASH: restaure-a primeiro) ou ainda em processamento (409 CAPTURE_PROCESSING); uma captura excluída permanentemente, ou cuja exclusão permanente está em andamento, resulta em 404. Cada requisição de retenção legal consome uma unidade do limite de requisições por minuto da conta (429 RATE_LIMITED com Retry-After quando ele se esgota). Enviar o estado que a captura já tem não altera nada e retorna 200 com os campos armazenados e `changed: false`. Cada alteração é registrada em auditoria como capture.legal_hold (a de uma captura do escritório, no tenant do escritório), registrada no histórico de acesso da captura (`legal_hold_on` / `legal_hold_off`, mantido quando a captura é apagada depois) e, quando a captura pertence a um caso, gravada no log de custódia do caso como capture.legal_hold_placed ou capture.legal_hold_released com o id da captura como alvo (o motivo vai para o log de acesso do caso, nunca para a cadeia com hash; a cadeia de um caso selado não muda). A resposta é { capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }.\n\nRetenção legal v2: aplicar ou liberar uma retenção também aplica ou remove uma retenção legal do S3 Object Lock em todas as versões do pacote armazenado, na sua prova OpenTimestamps atualizada e na cópia duplicada armazenada separadamente (`s3_legal_hold` no GET; quando o armazenamento recusa ou não tem configuração de Object Lock, a retenção no registro da captura continua valendo e uma tentativa malsucedida é repetida pelo servidor). Uma liberação inicia uma carência de 14 dias durante a qual a captura não é excluída permanentemente (não depois do fim da assinatura: os 30 dias após o fim a substituem). Uma retenção aplicada pela retenção legal do seu caso (`legal_hold_source` \"matter:<id>\") é liberada no caso enquanto essa retenção estiver ativa: liberá-la aqui resulta em 409 LEGAL_HOLD_MATTER (error.details.matter_id para quem pode ler o caso). Cada alteração é registrada na cadeia de eventos de captura da conta (GET /captures/events).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "restore (o padrão para um corpo sem private_note) ou legal_hold (com enabled e o reason opcional).",
                    "enum": [
                      "restore",
                      "legal_hold"
                    ]
                  },
                  "private_note": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PrivateNote"
                      }
                    ],
                    "description": "Define (uma string) ou apaga (null ou string vazia) a nota privada. Não pode ser combinado com action."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Com action legal_hold (obrigatório nesse caso): true aplica a retenção legal, false a libera."
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 200,
                    "description": "Com action legal_hold, opcional: por que a captura está retida, texto simples de no máximo 200 caracteres. Armazenado com a retenção, fora do pacote de provas; ignorado na liberação (nesse caso vai apenas para o log de auditoria)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Restaurada ({ capture_id, status, restored: true, matter_id?, matter_relinked? }), inalterada ({ capture_id, status }), nota privada definida ({ capture_id, status, private_note }) ou retenção legal definida ({ capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restored": {
                      "type": "boolean"
                    },
                    "matter_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "matter_relinked": {
                      "type": "boolean"
                    },
                    "private_note": {
                      "type": "string",
                      "nullable": true
                    },
                    "legal_hold": {
                      "type": "boolean",
                      "description": "Ação de retenção legal: a retenção da captura após a requisição."
                    },
                    "legal_hold_set_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Ação de retenção legal: quando a retenção atual foi aplicada (null quando não há nenhuma)."
                    },
                    "legal_hold_reason": {
                      "type": "string",
                      "nullable": true,
                      "description": "Ação de retenção legal: o motivo armazenado com a retenção atual, ou null."
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "Ação de retenção legal: false quando a captura já estava no estado solicitado (nada foi alterado nem registrado)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: JSON inválido, um corpo que não é um objeto, uma action diferente de restore ou legal_hold, private_note combinado com action, ou um private_note que não é string nem null, tem mais de 200 caracteres ou contém caracteres de controle, invisíveis ou de direção de texto. Para legal_hold: enabled ausente ou não booleano, um reason que não é string, tem mais de 200 caracteres ou não é texto simples, ou um campo desconhecido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a captura não é sua. FORBIDDEN também quando a captura pertence a um caso que não está selado e quem chama (o proprietário do escritório agindo sobre a captura de um assento, não o seu autor) não pode abrir esse caso. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave é somente leitura; use o painel). Para private_note: FORBIDDEN para qualquer pessoa exceto o autor da captura (que ainda precisa ter permissão para agir sobre ela). Para legal_hold: FORBIDDEN quando quem chama não pode agir sobre a captura; LEGAL_HOLD_OWNER_ONLY quando um assento (não o proprietário do escritório) libera a retenção de uma captura do escritório; LEGAL_HOLD_NOT_AVAILABLE ao aplicar uma retenção sem plano pago (details.reason plan) ou em uma captura em modo de teste (details.reason test_capture); EMAIL_NOT_VERIFIED para um endereço de e-mail não confirmado. A regra do caso acima não se aplica à retenção legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND, inclusive para uma captura que foi excluída permanentemente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Somente retenção legal. CAPTURE_IN_TRASH: a captura está na lixeira; restaure-a primeiro. CAPTURE_PROCESSING: a captura ainda está em processamento (error.details.status). LEGAL_HOLD_MATTER: a retenção da captura foi aplicada pela retenção legal do seu caso, que continua ativa; libere-a no caso. LEGAL_HOLD_SOURCE: a retenção de um tarjamento ou de uma comparação que acompanha a captura da qual foi gerado (no caso de uma comparação, qualquer uma das capturas comparadas), que continua sob retenção legal (aplicada a essa captura ou por meio do seu caso): o pacote permanece retido até que essa retenção seja liberada (`details.source_capture_id`). Uma comparação fica retida enquanto qualquer uma das capturas comparadas estiver retida e só é liberada quando nenhuma estiver (`legal_hold_source` \"derived:<capture id>\" indica a que está retida).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Somente retenção legal. RATE_LIMITED: o limite de requisições por minuto da conta foi esgotado. `error.details.retry_after_seconds` repete Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/verify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Capturas"
        ],
        "operationId": "verifyCapture",
        "summary": "Verificar um pacote armazenado",
        "description": "Lê o pacote armazenado e verifica a assinatura RSA do manifest.json e o SHA-256 de cada arquivo que ele lista. Pacotes maiores que 256 MiB retornam 413 VERIFY_PACKAGE_TOO_LARGE; verifique-os offline com os scripts incluídos. Cada requisição consome uma unidade do limite de requisições por minuto da conta (429 RATE_LIMITED). Quando o pacote traz manifest.json.tsr, o token do carimbo do tempo eIDAS também é verificado pelo ProofSnap (checks.eidas): a assinatura e os atributos assinados do token, se ele contém o SHA-256 do manifest.json, o extendedKeyUsage do certificado da TSA, sua cadeia de certificados até a CA raiz fixada Disig QCA4 no momento do token (genTime) e o status do certificado da TSA a partir da resposta OCSP ou da CRL registrada na emissão em eidas_validation.json (verificado pela assinatura, não consultado ao vivo). Esta verificação nunca solicita um novo carimbo do tempo. Ela também informa se manifest.json.ots está presente e se o hash do manifesto consta no registro de emissões do ProofSnap. O veredito depende apenas da assinatura do manifesto e dos arquivos listados; um token que falha na verificação é informado em checks.eidas e em warnings (EIDAS_TOKEN_FAILED), sem entrar no veredito. Um pacote emitido com carimbo do tempo qualificado registra eidas_requested: true em seu manifest.json assinado; quando esse pacote não traz mais manifest.json.tsr, checks.eidas e checks.eidas_disig são informados como missing com o aviso EIDAS_TOKEN_MISSING (os scripts de verificação incluídos informam FAIL para o mesmo pacote). Permitido ao proprietário da captura, aos membros do caso dela e ao proprietário do escritório para o qual ela foi feita. Para verificar os carimbos do tempo de forma independente, execute os scripts da pasta verification/ do pacote.\n\nPacotes importados (source import): o ZIP armazenado é verificado novamente com as verificações de importação (assinatura, cada arquivo, custódia, cadeia forense, carimbos do tempo; a procedência é consultada novamente no registro da API para pacotes da API e obtida do registro de importação para pacotes da extensão). A resposta é { capture_id, source, type: \"import\", package_kind, verifier: \"package-verify\", verdict (match, modified, invalid), rechecked: true, manifest_sha256, zip_sha256, zip_matches_import, kind, summary, levels, checks, reasons, provenance }. Um pacote acima de 512 MiB, ou uma verificação que não termina em cerca de 20 segundos, retorna rechecked: false com um motivo (too_large_for_synchronous_check, time_budget, storage_error), verdict: null e stored_verification (o relatório registrado no momento da importação).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado da verificação.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                },
                "example": {
                  "verdict": "match",
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "source": "s3",
                  "checks": {
                    "manifest_signature": {
                      "status": "valid",
                      "algorithm": "RSA-4096"
                    },
                    "declared_files": {
                      "status": "all_match"
                    },
                    "bitcoin_ots": {
                      "status": "present"
                    },
                    "eidas_disig": null,
                    "proofsnap_issuance": {
                      "status": "issued_by_proofsnap",
                      "manifest_sha256": "3f2a...",
                      "issued_at": "2026-09-01T10:00:00.000Z",
                      "type": "capture"
                    },
                    "eidas": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: desconhecida, excluída ou sem pacote armazenado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "VERIFY_PACKAGE_TOO_LARGE: o pacote armazenado tem mais de 256 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: o limite de requisições por minuto da conta se esgotou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: não foi possível ler o pacote armazenado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/redact": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Comparar e tarjar"
        ],
        "operationId": "redactCapture",
        "summary": "Criar uma cópia tarjada",
        "description": "Gera um novo pacote a partir de uma captura concluída com o conteúdo selecionado removido, assina-o novamente (nova chave RSA-4096) e o envia para uma ancoragem OpenTimestamps. O original não é alterado. Síncrono: retorna 201 quando o pacote está pronto. Cobrado como uma captura.\n\n- Tarjas de área cobrem de preto regiões da captura de tela. Uma captura de tela JPEG no formato em que a captura a grava (sequencial, em tons de cinza ou YCbCr) é tarjada sem ser decodificada em pixels: os blocos comprimidos (MCUs, de 8 a 32 pixels de lado) que uma tarja toca são substituídos por blocos pretos e todos os demais blocos são mantidos inalterados, de modo que o restante da imagem não é recomprimido (`screenshot_method` `jpeg_dct_blocks`, até 150 megapixels). A área coberta de preto é cada tarja arredondada para blocos inteiros (`painted_rects`), portanto sempre contém a tarja. Uma captura de tela PNG, ou um JPEG de outro tipo, é decodificada, pintada e recodificada como screenshot.jpeg, e então os blocos de 8 x 8 que uma tarja toca são cobertos de preto no novo arquivo (`screenshot_method` `decode_repaint_jpeg`, até 60 megapixels). Uma captura de tela acima do seu limite ou com dados de imagem que não podem ser lidos é recusada com 422 REDACTION_SCREENSHOT_UNREADABLE antes de qualquer cobrança. Se o cabeçalho puder ser lido mas os dados de imagem não, o erro é detectado depois que a unidade mensal foi reservada, e essa unidade é liberada. Tarjamentos de texto e de metadados continuam funcionando sem tarjas de área. O redaction_manifest.json (`screenshot_boxes`) registra o método, `painted_rects`, a grade de blocos e uma nota explicativa para peritos forenses.\n- Tarjamentos de texto removem as ocorrências de page.html e domtextcontent.txt, de todos os valores de texto livre de metadata.json (título da página e valores meta, registros WHOIS / RDAP, o subject do TLS, valores de cabeçalhos de resposta HTTP) e de todos os valores de texto livre de forensic_log.json e chain_of_custody.json (case_reference, case_id, case_note, os seus valores de metadados, código injetado e seus erros, os rótulos de pop-ups fechados, legal_context.note). Identificadores, hashes, horários e URLs nesses registros permanecem inalterados. Ocorrências codificadas também são removidas: referências de caracteres HTML (nomeadas, decimais, hexadecimais, como M&uuml;ller ou jan&#64;firma.cz), codificação percentual (mailto:jan%40firma.cz) e escapes JavaScript (\\u0040, \\x40), incluindo codificações aninhadas em até dois níveis; o marcador substitui os próprios caracteres codificados. A verificação pós-tarjamento de ocorrências remanescentes lê os arquivos JSON valor por valor e busca em cada texto tanto na forma escrita quanto decodificada, de modo que falha diante de uma ocorrência em qualquer dessas formas. Tarjamentos de texto também cobrem de preto a captura de tela onde o text_layout.json da captura de origem posiciona uma ocorrência (`text_rule_rects`): são pintadas as linhas estimadas do bloco de texto que contêm a ocorrência, e o bloco inteiro somente quando a estimativa é incerta (contado em `screenshot_boxes.from_text_rules.rects_whole_block` do redaction_manifest.json); as ocorrências que ele não posiciona são contadas em `text_occurrences_not_located`. As regras diferenciam maiúsculas de minúsculas, a menos que `case_insensitive` esteja definido.\n- Tarjamentos de metadados removem campos de metadata.json. Dois nomes vão além. `ip_dns` (também aceitos: `ip`, `dns`, `resolved_ips`) remove os endereços IP e as respostas DNS do site de destino de metadata.json, forensic_log.json e chain_of_custody.json (os endereços da própria máquina de captura permanecem) e também os remove, apenas endereços inteiros, de page.html, domtextcontent.txt e da captura de tela como regras de texto automáticas (registradas apenas com a origem ip_dns, nunca o endereço nem um hash dele, e não contadas no limite de text_redactions). `cookie_names` (também `cookie`, `cookies`, `http_headers.cookie`) remove os cabeçalhos Cookie e Set-Cookie e todas as listas de nomes de cookies nesses registros; as contagens são mantidas. Os valores de cookies já são tarjados em todas as capturas.\n\nUma tarja de área cobre apenas pixels. O texto sob uma tarja permanece em page.html, domtextcontent.txt e metadata.json, a menos que você também adicione um tarjamento de texto, e uma imagem sob uma tarja ainda pode estar referenciada ou incorporada em page.html (img src ou srcset, CSS url(), um URI data:); um tarjamento de texto que corresponda ao endereço dela remove essa referência. O pacote tarjado traz do original apenas screenshot.jpeg (ou screenshot.png quando nenhuma tarja o substituiu), page.html, domtextcontent.txt, metadata.json e forensic_log.json, além dos arquivos que ele grava de novo (evidence.pdf, chain_of_custody.json, redaction_manifest.json, os arquivos de verificação e os arquivos de manifesto). Todos os demais arquivos do original, como images/, image_inventory.json, text_layout.json, page.mhtml, network.har e capture.warc, ficam de fora e são listados no redaction_manifest.json em dropped_for_leak_protection. Os tarjamentos de texto também são aplicados a esses nomes de arquivo, porque uma cópia de imagem recebe o nome da sua URL. O chain_of_custody.json mantém os eventos do original sobre eles, marcados como artifact_removed_in_redaction e sem os hashes dos arquivos que o pacote tarjado não contém. Um rule_902_certification.pdf do original também é removido, porque ele enumera os arquivos e hashes do original; a remoção é listada no redaction_manifest.json (removed_as_stale) e no chain_of_custody.json. A URL da captura nunca é tarjada, porque faz parte do manifesto assinado: um tarjamento de texto cujo texto também está na URL da captura ou na URL final é aplicado em todos os outros lugares, o texto permanece nessa URL e a regra é listada em `rules_in_capture_url` e no redaction_manifest.json (leakage_check.capture_url). Somente capturas de páginas web podem ser tarjadas (uma comparação, certificação ou tarjamento é recusado com 422 REDACTION_SOURCE_UNSUPPORTED).\n\nUm tarjamento de uma captura de teste (feita com uma chave de teste, no sandbox ou por uma conta sem conta de cobrança vinculada) é um pacote de teste independentemente da chave que o solicita: com marca d'água, `test_mode: true` em manifest.json, uma linha de teste (key_env test) que não é cobrada e não usa nenhuma unidade mensal. include_eidas nesse tarjamento é recusado com 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nO relatório de tarjamento (evidence.pdf) é gerado em `language` (padrão: o idioma da captura de origem), incluindo o corpo, a capa e a faixa de teste.\n\nAntes de qualquer tarjamento, todo arquivo que o tarjamento lê ou carrega (a captura de tela, page.html, domtextcontent.txt, metadata.json, forensic_log.json, chain_of_custody.json, text_layout.json) e que o manifesto de origem lista com um SHA-256 tem o hash recalculado (redaction_manifest.json derived_from.source_files_rehashed); uma divergência ou um arquivo ausente retorna 422 REDACTION_SOURCE_INTEGRITY_FAILED e nada é emitido, de modo que uma origem corrompida ou alterada nunca é selada novamente sob uma nova assinatura. Os arquivos que o pacote tarjado deixa de fora não são lidos. Um tarjamento síncrono cuja origem demorou demais para ser lida é interrompido antes de qualquer reserva, com 500 CAPTURE_TIME_BUDGET_EXCEEDED.\n\nUm tarjamento de uma captura sob retenção legal também fica retido (legal_hold_source \"derived:<capture id>\"), assim como todo tarjamento de uma captura quando uma retenção é aplicada a ela; liberar a retenção da captura libera essas retenções e, enquanto a captura continuar retida, elas não podem ser liberadas isoladamente (409 LEGAL_HOLD_SOURCE). O captured_at de uma linha de tarjamento é o horário de captura da sua origem; redacted_at é quando o tarjamento foi feito.\n\nO tarjamento falha de forma fechada: quando a verificação pós-tarjamento ainda encontra um termo tarjado em qualquer lugar do pacote fora da URL da captura e da URL final, nenhum pacote é emitido e a requisição retorna 422 REDACTION_LEAK_DETECTED (`error.details` `{ files, rule_indexes, metadata_fields, in_capture_url }`; o termo em si nunca é repetido). O redaction_manifest.json registra `leakage_check.fail_closed: true`.\n\nOs valores são truncados em limites de caractere (um caractere nunca é dividido): strings de correspondência de texto em 2000 caracteres, motivos em 200, reason_summary em 500, campos do custodiante em 200. Motivos, reason_summary e campos do custodiante são impressos no certificado de tarjamento e devem ser texto simples: um caractere de controle, invisível ou de direção de texto é recusado com 400.\n\nOrçamento de tempo e novas tentativas: a requisição retorna dentro do limite de 29 segundos do API Gateway. A selagem é executada sob um orçamento de tempo contado a partir da requisição; se o carimbo do tempo qualificado (include_eidas) não couber no orçamento, ele não é solicitado e o pacote é entregue apenas com a ancoragem OpenTimestamps (timestamps.eidas com status not_obtained, motivo time_budget, não cobrado), e uma requisição OpenTimestamps que não couber fica de fora (o chain_of_custody.json registra ots_skipped com motivo time_budget). Envie um Idempotency-Key para que uma nova tentativa (por exemplo, após um 504 de um gateway ou um erro de rede) retorne o mesmo tarjamento em vez de criar e cobrar um segundo. Uma nova tentativa enquanto a primeira requisição ainda está em execução retorna 409 IDEMPOTENCY_IN_PROGRESS com o ID do tarjamento em `error.details.capture_id` (consulte GET /captures/{capture_id} com ele). Uma nova tentativa posterior retorna 201 com o corpo original e `Idempotent-Replay: true`. Um tarjamento que falha libera a sua chave, de modo que uma nova tentativa com a mesma chave é executada novamente.\n\nLimite de requisições por conta em janelas de 60 segundos (todas as chaves de API da conta e a sua sessão do painel compartilham uma janela). Depois que o limitador é executado, as respostas desta rota incluem os cabeçalhos `RateLimit-Policy` e `RateLimit` (com base no IETF draft-ietf-httpapi-ratelimit-headers), além dos legados `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Um 429 acrescenta `Retry-After`.\n\nUm pacote de provas importado (source import, IDs com o prefixo `imp_`) não pode ser tarjado: 422 IMPORT_NOT_SUPPORTED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boxes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "minimum": 0
                        },
                        "y": {
                          "type": "number",
                          "minimum": 0
                        },
                        "w": {
                          "type": "number",
                          "minimum": 0
                        },
                        "h": {
                          "type": "number",
                          "minimum": 0
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "x",
                        "y",
                        "w",
                        "h"
                      ]
                    },
                    "maxItems": 200,
                    "description": "Retângulos em pixels da captura de tela da captura de origem (x e y a partir do canto superior esquerdo, w e h a largura e a altura). Quando os quatro valores de uma tarja estão entre 0 e 1, eles são lidos como frações da largura e da altura da captura de tela. Uma tarja que fica inteiramente fora da captura de tela não pinta nada e é listada em ignored_boxes; uma tarja que ultrapassa uma borda é cortada na borda e listada em clipped_boxes. Quando todas as tarjas ficam de fora e nenhum tarjamento de texto ou de metadados altera nada, a requisição é recusada com 400 REDACTION_NO_OPS antes de qualquer cobrança."
                  },
                  "text_redactions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "match": {
                          "oneOf": [
                            {
                              "type": "string",
                              "description": "Texto literal.",
                              "minLength": 1
                            },
                            {
                              "type": "object",
                              "properties": {
                                "pattern": {
                                  "type": "string",
                                  "description": "Expressão regular, de 1 a 500 caracteres. Padrões com quantificadores aninhados, como (a+)+, são rejeitados com 400.",
                                  "minLength": 1,
                                  "maxLength": 500
                                },
                                "flags": {
                                  "type": "string",
                                  "description": "Qualquer um de g, i, m, s, u, cada um no máximo uma vez. y (sticky) é recusado com 400: ele só corresponderia em uma única posição e deixaria as demais ocorrências.",
                                  "pattern": "^[gimsu]*$"
                                }
                              },
                              "required": [
                                "pattern"
                              ]
                            }
                          ]
                        },
                        "case_insensitive": {
                          "type": "boolean",
                          "default": false,
                          "description": "Corresponde a uma string literal sem diferenciar maiúsculas e minúsculas (para uma expressão regular, use a flag i)."
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200,
                          "description": "Texto simples, impresso no certificado de tarjamento."
                        }
                      },
                      "required": [
                        "match"
                      ]
                    },
                    "maxItems": 200
                  },
                  "metadata_fields": {
                    "type": "array",
                    "description": "Caminhos de campos em metadata.json (um segmento `*` corresponde a todos os itens de um array), ou os nomes `ip_dns`, `cookie_names` e `authorization` (veja acima).",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "Caminho do campo em metadata.json."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field"
                          ]
                        }
                      ]
                    },
                    "maxItems": 100
                  },
                  "reason_summary": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Texto simples, impresso no certificado de tarjamento."
                  },
                  "custodian": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      }
                    },
                    "description": "Impresso no relatório de tarjamento. name e title seguem a regra de texto simples (veja PlainText)."
                  },
                  "language": {
                    "type": "string",
                    "description": "Idioma dos PDFs legíveis por humanos. Um de: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Maiúsculas, hífen e região são tolerados (\"DE\", \"fr-FR\" e \"PT-BR\" são aceitos); \"pt\" e \"zh\" sozinhos são rejeitados. Um valor não suportado retorna 400 MALFORMED_REQUEST. Opcional: sem ele (ou em branco), o relatório fica no idioma da captura de origem."
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Como em CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "URL https que recebe redaction.completed. Ela é enviada logo após a resposta 201, não antes. A URL deve ser https e não pode apontar para um endereço privado, de loopback, link-local, de NAT de nível de operadora (CGNAT) ou de metadados de nuvem (as mesmas regras de uma URL de captura; caso contrário, 422 SSRF_BLOCKED). Um valor vazio ou só com espaços significa nenhum webhook. Exige uma chave de API: enviado a partir de uma sessão do painel, é recusado com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave.",
                    "format": "uri"
                  },
                  "rule_commitment_salt": {
                    "type": "string",
                    "description": "Opcional. Seu próprio salt de 32 bytes para os compromissos das regras (redaction_manifest.json rule_commitments), como 64 dígitos hexadecimais ou base64 / base64url; um salt formado por um único byte repetido é recusado (400). Envie-o para ter o salt em mãos antes da chamada: sem ele, o servidor gera um e o retorna somente na resposta 201, de modo que uma resposta perdida por timeout (504 do gateway enquanto o tarjamento ainda foi concluído) perde o salt, e um replay com Idempotency-Key não o traz. ProofSnap não o guarda."
                  }
                },
                "description": "Pelo menos um entre boxes, text_redactions ou metadata_fields."
              },
              "example": {
                "text_redactions": [
                  {
                    "match": "jane.doe@example.com",
                    "reason": "personal data"
                  }
                ],
                "reason_summary": "Personal data of a third party"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pacote tarjado pronto. Um replay idempotente de um tarjamento anterior com o mesmo Idempotency-Key também retorna 201, com `Idempotent-Replay: true`: o corpo original, com o status atual e uma package_url recém-pré-assinada (omitida quando o pacote não pode ser pré-assinado; leia-o com GET /captures/{capture_id}).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redaction_id": {
                      "type": "string",
                      "example": "red_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "redacted_from": {
                      "type": "string"
                    },
                    "redaction_count": {
                      "type": "integer",
                      "description": "Número de ações de tarjamento aplicadas."
                    },
                    "original_signature_verified": {
                      "type": "boolean",
                      "description": "Se a assinatura original do manifesto foi verificada antes do tarjamento."
                    },
                    "leakage_warnings": {
                      "type": "integer",
                      "description": "Sempre 0: um tarjamento cuja verificação pós-tarjamento encontra um termo remanescente é recusado com 422 REDACTION_LEAK_DETECTED em vez de ser emitido. Mantido por compatibilidade."
                    },
                    "package_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "package_url_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verify_url": {
                      "type": "string",
                      "description": "Link para o verificador público do ProofSnap.",
                      "format": "uri"
                    },
                    "ignored_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Índices, começando em zero, nas boxes da requisição, das boxes que ficaram inteiramente fora da captura de tela e não pintaram nada. Também registrado em redaction_manifest.json (screenshot_boxes)."
                    },
                    "clipped_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Índices baseados em zero, dentro dos boxes da requisição, das áreas que ultrapassaram a borda da captura de tela e foram cortadas nela. Também registrados em redaction_manifest.json (screenshot_boxes)."
                    },
                    "screenshot_method": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "jpeg_dct_blocks",
                        "decode_repaint_jpeg",
                        null
                      ],
                      "description": "Como as áreas foram pintadas: jpeg_dct_blocks (os blocos comprimidos do JPEG sob cada área substituídos por blocos pretos, todos os outros blocos inalterados) ou decode_repaint_jpeg (a captura de tela decodificada, pintada e recodificada). null quando nenhuma área pintou nada. Também registrado em redaction_manifest.json (screenshot_boxes.method)."
                    },
                    "painted_rects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "x": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "y": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "w": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "h": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "required": [
                          "x",
                          "y",
                          "w",
                          "h"
                        ]
                      },
                      "description": "As áreas efetivamente cobertas de preto, em pixels da captura de tela, uma por tarja aplicada, na ordem dos boxes da requisição, sem os ignorados. Com jpeg_dct_blocks, cada uma é a tarja arredondada para fora até blocos inteiros, de modo que contém a tarja; com decode_repaint_jpeg, é a tarja limitada à captura de tela e arredondada para fora até blocos inteiros de 8 x 8 do novo arquivo, que são cobertos de preto após a recodificação para que a compressão não leve cores vizinhas para dentro da tarja. Também registrado em redaction_manifest.json (screenshot_boxes.painted_rects)."
                    },
                    "text_rule_rects": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Áreas distintas da captura de tela tarjadas de preto apenas pelos tarjamentos de texto, sem as áreas que contêm uma tarja de área (uma área que duas regras atingem conta uma vez): nos blocos de texto do text_layout.json da origem cujo texto uma regra atinge, as linhas estimadas que contêm a correspondência, ou o bloco inteiro quando a estimativa é incerta. Listadas em redaction_manifest.json (screenshot_boxes.from_text_rules)."
                    },
                    "text_occurrences_not_located": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Ocorrências dos tarjamentos de texto no texto da página que não puderam ser localizadas na captura de tela e não foram cobertas de preto ali (texto que o text_layout.json da origem não lista, como texto dentro de uma imagem; um bloco grande demais para ser coberto por um único termo; áreas além do limite de um tarjamento; uma captura de tela que não pôde ser pintada). Ocorrências em um bloco totalmente fora da captura de tela (nada ali a cobrir de preto) não são contadas aqui; redaction_manifest.json as lista como occurrences_not_on_screenshot. Nunca mais do que as ocorrências no texto da página. Confira a captura de tela e adicione áreas. redaction_manifest.json (leakage_check.screenshot_text_advisory) traz as contagens por regra."
                    },
                    "text_variants_remaining": {
                      "type": "object",
                      "description": "Apenas informativo, nunca uma falha: por regra de texto literal, as ocorrências que restaram no domtextcontent.txt deste pacote e no texto do seu page.html que a regra não encontrou, mas que coincidem quando maiúsculas/minúsculas e diacríticos são ignorados (por exemplo \"boskovické\" para a regra sensível a maiúsculas \"Boskovic\"), e se o termo está na URL da captura ou na URL final, que nunca são tarjadas. `total` soma, por regra, a maior das duas contagens (os dois arquivos contêm o mesmo texto da página). Somente contagens, nunca o texto. Regras de regex não são contadas. Também em redaction_manifest.json (leakage_check.text_variants_advisory). Presente quando a requisição tem tarjamentos de texto.",
                      "properties": {
                        "total": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "per_rule": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rule_index": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_domtextcontent": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_page_html_text": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_capture_url": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "rules_in_capture_url": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Presente quando a requisição tem tarjamentos de texto. Índices, a partir de zero, das regras de texto cujo texto também está na URL da captura ou na URL final. Essas URLs nunca são alteradas: o texto foi removido de todos os outros lugares e continua visível ali. Vazio quando nenhuma regra corresponde a elas. Também em redaction_manifest.json (leakage_check.capture_url, `remains_in_capture_url: true` por regra) e no relatório de tarjamento."
                    },
                    "rule_commitment_salt": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "Presente quando a requisição tem tarjamentos de texto. O salt de 32 bytes (hex; o rule_commitment_salt da requisição, quando enviado) dos rule_commitments de redaction_manifest.json: para cada regra de texto, o SHA-256 do salt da regra seguido dos bytes UTF-8 do JSON canônico da regra {\"match\": <text, or {\"pattern\", \"flags\"}>, \"case_insensitive\": <bool>}, em que o salt da regra é um HMAC-SHA256 com este salt como chave sobre o texto UTF-8 \"rule:<rule_index>\" (assim, uma regra pode ser demonstrada revelando apenas o próprio salt, sem as demais). As regras não são armazenadas, e o salt é retornado somente nesta resposta: não em um replay de Idempotency-Key, em um webhook, no log de eventos nem em GET /captures/{capture_id}, e o ProofSnap não o guarda. Guarde-o com seus registros: com ele, você pode demonstrar quais regras o tarjamento aplicou."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason; por exemplo, um motivo que não é texto simples ou a flag de regex y), REDACTION_NO_OPS (nada informado, ou nada correspondeu) ou INVALID_URL (webhook_url não é https). WEBHOOK_REQUIRES_API_KEY: webhook_url enviado de uma sessão do painel (webhooks exigem uma chave de API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED ou PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: a captura não é sua. EIDAS_REQUIRES_LIVE_KEY: include_eidas com uma chave de teste, ou para um tarjamento de uma captura de teste (então com details.reason). EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria trabalho novo. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave é somente leitura; use o painel). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel ProofSnap (trabalho novo nesse plano é criado somente no painel; uma barreira leve, não uma fronteira de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "REDACTION_NOT_READY: a captura de origem não está concluída. IDEMPOTENCY_IN_PROGRESS: uma requisição com o mesmo Idempotency-Key ainda está em execução (details.capture_id é o ID do tarjamento a consultar com GET /captures/{capture_id}). COMPANY_READ_ONLY: a origem pertence a um escritório mantido como Company archive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "REDACTION_SCREENSHOT_UNREADABLE: não é possível ler os dados de imagem da captura de tela ou a captura de tela está acima do limite da tarja de área (150 megapixels para um JPEG como a captura o grava, 60 megapixels para um PNG ou outro JPEG); os tarjamentos de texto e de metadados continuam funcionando sem tarjas de área. REDACTION_LEAK_DETECTED: um termo tarjado sobreviveu fora da URL da captura (details { files, rule_indexes, metadata_fields, in_capture_url }; in_capture_url é true quando o texto da regra recusada também está na URL da captura ou na URL final, onde nunca é contado). REDACTION_SOURCE_UNSUPPORTED: a origem não é uma captura de página web (details { source } ou { manifest_type }). SSRF_BLOCKED: webhook_url aponta para um endereço bloqueado. REDACTION_SOURCE_INTEGRITY_FAILED: um arquivo do pacote de origem não corresponde ao SHA-256 listado em seu manifesto assinado, ou um arquivo listado está ausente (details { mismatched, missing }, apenas nomes de arquivo); nada foi tarjado nem cobrado. IDEMPOTENCY_KEY_REUSED: o Idempotency-Key já foi usado com um corpo de requisição diferente (ou a partir de outro ambiente de chave).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete o Retry-After. RateLimit informa r=0 e os segundos até a janela ser reiniciada. Também RATE_LIMITED, para uma sessão do painel de Website Archive: o limite diário de 100 unidades por dia UTC, compartilhado por capturas individuais e em lote (uma unidade por URL), compare, redact e certify-hash (uma unidade por requisição); uma requisição que não cabe é recusada por inteiro (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After e details.retry_after_seconds dão os segundos até o próximo dia UTC, e os cabeçalhos por minuto RateLimit e X-RateLimit-* são omitidos). Uma recusa curta com apenas details.retry_after_seconds e Retry-After: 1 indica uma atualização concorrente do contador: tente de novo. Uma recusa que não é culpa do chamador devolve as unidades; trabalho aceito com 202 que falha depois mantém a sua unidade. Uma barreira branda, não um limite de segurança.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: erro inesperado do servidor; tente novamente com backoff exponencial. CAPTURE_TIME_BUDGET_EXCEEDED: o tarjamento síncrono esgotou o seu orçamento de tempo e foi interrompido antes de qualquer pacote ser emitido; nada foi cobrado. Com `details.reason` `source_read`, a leitura do pacote de origem consumiu a maior parte do orçamento: o pacote é grande demais para ser tarjado agora, tente novamente mais tarde.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/compare": {
      "post": {
        "tags": [
          "Comparar e tarjar"
        ],
        "operationId": "compareCaptures",
        "summary": "Comparar duas capturas",
        "description": "Compara duas das suas capturas concluídas (texto da página, as imagens exibidas na página, metadados) e gera um pacote de comparação assinado. Retorna 202; consulte GET /captures/{compare_id}, que passa a trazer type, compared e diff_summary. As imagens são comparadas a partir do inventário de imagens que cada captura de origem selou (image_inventory.json: o endereço de cada imagem exibida e o SHA-256 dos seus bytes), nunca pixel a pixel; os resultados ficam em image_changes.json e em diff_summary.image_changes. Uma origem capturada antes de as capturas registrarem um inventário de imagens ainda é comparada, sem a comparação de imagens (diff_summary.image_changes.status not_run). Cobrada como uma captura. Igual com igual (2026-10-14): duas capturas renderizadas com um perfil de dispositivo diferente ou com configurações de captura diferentes são recusadas com 422 COMPARE_PROFILE_MISMATCH (details.differing, details.before, details.after: apenas os campos divergentes, CSS e JavaScript injetados como seu SHA-256), a menos que allow_profile_mismatch seja true, e duas capturas de páginas diferentes com 422 COMPARE_URL_MISMATCH (details.before e details.after: url e final_url como origem e caminho), a menos que allow_url_mismatch seja true. A verificação é feita primeiro nos dois registros de captura (antes de qualquer leitura ou reserva) e depois no metadata.json e no forensic_log.json selados de ambos os pacotes, dentro da verificação de origem descrita abaixo; uma divergência encontrada apenas durante a execução da comparação faz a comparação falhar com esse código e devolve a unidade mensal. O texto é comparado linha a linha com um diff de linhas determinístico (sem timeout): diff_summary conta linhas (text_unit line). Somente capturas web (feitas pela API ou por um agendamento) podem ser comparadas: uma certificação de hash, uma comparação ou um pacote tarjado é recusado com 422 COMPARE_SOURCE_UNSUPPORTED (`details.capture_id`, `details.source`) antes da leitura de qualquer pacote e antes de qualquer reserva. Antes de qualquer comparação ou reserva, o SHA-256 de cada arquivo listado com SHA-256 no manifest.json de cada pacote de origem é recalculado; uma origem com um arquivo alterado ou ausente, ou um manifesto que não lista nenhum arquivo com SHA-256, é recusada com 422 COMPARE_SOURCE_INTEGRITY_FAILED (sem comparação, sem registro, sem unidade mensal). Somente esses arquivos listados são comparados: um arquivo dentro de um pacote de origem que o manifesto não lista com SHA-256 (por exemplo um text_layout.json, image_inventory.json, page.html ou domtextcontent.txt acrescentado) é tratado como ausente. A verificação é feita na própria requisição quando ambos os pacotes de origem têm 50 MB ou menos e ela termina em cerca de 10 segundos; caso contrário, a requisição é aceita e a execução da comparação repete a verificação, e uma origem com falha faz então a comparação falhar (GET /captures/{compare_id} mostra status failed com error.code COMPARE_SOURCE_INTEGRITY_FAILED) e devolve a unidade mensal. Uma comparação concluída registra essa verificação em compare.json `sources[].source_files_reverified` e em comparison_method.json `source_verification`. /compare não envia webhooks: um webhook_url no corpo é ignorado. Consulte GET /captures/{compare_id}.\n\nUma comparação que envolve uma captura de teste (feita com uma chave de teste, no sandbox ou por uma conta sem conta de cobrança vinculada) é um pacote de teste, independentemente da chave que a solicita: com marca d'água, `test_mode: true` no manifest.json, `test_mode: true` para essa origem em compare.json `sources[]`, e um registro de teste (key_env test) que não é cobrado e não consome unidade mensal. include_eidas com uma origem de teste é recusado com 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nLimite de requisições por conta em janelas de 60 segundos (todas as chaves de API da conta e a sua sessão do painel compartilham uma janela). Depois que o limitador é executado, as respostas desta rota incluem os cabeçalhos `RateLimit-Policy` e `RateLimit` (baseados no IETF draft-ietf-httpapi-ratelimit-headers), além dos legados `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Um 429 acrescenta `Retry-After`.\n\nEnvie um Idempotency-Key para que uma nova tentativa (por exemplo após um 504) retorne a primeira resposta em vez de uma segunda execução cobrada (24 horas, mesmas regras de POST /captures). A chave é reservada antes da verificação de origem, então uma nova tentativa enquanto a primeira requisição ainda está em execução retorna 409 IDEMPOTENCY_IN_PROGRESS; uma requisição recusada pela verificação de origem ou pela cota libera a sua chave, então uma nova tentativa com a mesma chave é verificada novamente.\n\nUm pacote de provas importado (source import, IDs com prefixo `imp_`) não pode ser comparado: 422 IMPORT_NOT_SUPPORTED (details.capture_id).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_id_1": {
                    "type": "string",
                    "description": "A captura anterior."
                  },
                  "capture_id_2": {
                    "type": "string",
                    "description": "A captura posterior. Deve ser diferente de capture_id_1."
                  },
                  "language": {
                    "type": "string",
                    "description": "Idioma do PDF de comparação. Somente o código exato; qualquer outro valor volta silenciosamente para o inglês.",
                    "enum": [
                      "en",
                      "de",
                      "it",
                      "fr",
                      "es",
                      "pt_BR",
                      "cs",
                      "el",
                      "hi",
                      "hu",
                      "id",
                      "ja",
                      "ko",
                      "nl",
                      "pl",
                      "ro",
                      "ru",
                      "sv",
                      "tr",
                      "vi",
                      "zh_CN",
                      "zh_TW",
                      "ar",
                      "he",
                      "da",
                      "fi",
                      "nb",
                      "sk",
                      "uk"
                    ]
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Como em CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Adiciona um modelo de certificação não assinado (rule_902_certification.pdf, veja CaptureOptions.include_rule_902) à comparação. Não disponível para capturas renderizadas de forma diferente: com allow_profile_mismatch true e divergência de perfil ou de configurações, 422 RULE902_NOT_AVAILABLE_FOR_MISMATCH. Desativado no momento: o modelo de certificação está temporariamente desligado, então este campo é aceito e ignorado (nenhum rule_902_certification.pdf é gerado ou listado no pacote) e a resposta traz o aviso RULE902_DISABLED.",
                    "default": false
                  },
                  "allow_profile_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Compara duas capturas mesmo que tenham sido feitas com um perfil de dispositivo diferente (options.viewport mobile ou desktop, viewport_width, viewport_height, device_scale_factor; selados: tamanho da viewport, fator de escala do dispositivo, emulação mobile, user agent sem números de versão) ou com configurações de captura diferentes que mudam a renderização da página (dark_mode, block_ads, block_resources, inject_css, inject_js por SHA-256, expand_collapsed, dismiss_consent, dismiss_popups, página inteira). Sem isso, esse par é recusado com 422 COMPARE_PROFILE_MISMATCH. Com isso, diff_summary.text_verdict e images_verdict ficam undetermined, changed é null a menos que um campo de metadados de conteúdo seja diferente, e include_rule_902 é recusado."
                  },
                  "allow_url_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Comparar duas capturas de páginas diferentes. Duas capturas contam como a mesma página quando um endereço de uma (a url solicitada, a url e a final_url que o seu metadata.json registra) é igual a um da outra após a normalização (host em minúsculas, http e https equivalentes, porta padrão, fragmento, barra final e os parâmetros de consulta voláteis desconsiderados). Sem isso, páginas diferentes são recusadas com 422 COMPARE_URL_MISMATCH."
                  }
                },
                "required": [
                  "capture_id_1",
                  "capture_id_2"
                ]
              },
              "example": {
                "capture_id_1": "cap_a1B2c3D4e5F6g7H8",
                "capture_id_2": "cap_h8G7f6E5d4C3b2A1"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "compare_id": {
                      "type": "string",
                      "example": "cmp_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: JSON inválido, um ID ausente, o mesmo ID duas vezes, ou allow_profile_mismatch / allow_url_mismatch que não é booleano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED ou PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: uma captura não é sua. EIDAS_REQUIRES_LIVE_KEY: include_eidas com uma chave de teste, ou com uma captura de origem de teste (nesse caso com details.reason). EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria novos trabalhos. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o painel). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel do ProofSnap (novos trabalhos nesse plano são criados apenas no painel; uma barreira leve, não uma fronteira de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "COMPARE_NOT_READY: as duas capturas precisam estar concluídas. COMPANY_READ_ONLY: uma origem pertence a um escritório mantido como company archive (nada novo é criado a partir dela).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "COMPARE_SOURCE_INTEGRITY_FAILED: um arquivo de uma captura de origem não corresponde ao SHA-256 listado no seu manifesto assinado, ou está ausente; nada foi comparado nem cobrado. `details.capture_id`, `details.mismatched`, `details.missing` indicam a primeira origem com falha; `details.reason` está presente quando o próprio manifesto de origem não pode ser usado (por exemplo \"the source manifest lists no file with a SHA-256\" ou \"the source manifest lists no files\"); `details.sources[]` lista todas as origens com falha, com os mesmos campos. Verifique a captura de origem (POST /captures/{capture_id}/verify). COMPARE_SOURCE_UNSUPPORTED: uma origem não é uma captura web (uma certificação de hash, uma comparação ou um pacote tarjado); nada foi lido, comparado nem cobrado. `details.capture_id` indica essa origem e `details.source` o seu tipo, quando o registro o contém. Compare as capturas web originais. COMPARE_PROFILE_MISMATCH: as duas capturas foram feitas com um perfil de dispositivo diferente ou com configurações de captura diferentes; `details.differing` lista os nomes dos campos (no nível do registro: viewport, viewport_width, viewport_height, device_scale_factor, dark_mode, block_ads, block_resources, inject_css, inject_js, expand_collapsed, dismiss_consent, dismiss_popups, full_page; no nível selado: viewport_width, viewport_height, device_scale_factor, is_mobile, user_agent e as mesmas configurações), `details.before` e `details.after` os seus valores. Envie allow_profile_mismatch: true para comparar mesmo assim. COMPARE_URL_MISMATCH: as duas capturas são de páginas diferentes; `details.before` e `details.after` informam url e final_url como origem e caminho. Envie allow_url_mismatch: true para comparar mesmo assim. RULE902_NOT_AVAILABLE_FOR_MISMATCH: include_rule_902 com allow_profile_mismatch para capturas renderizadas de forma diferente; `details.differing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete o Retry-After. RateLimit informa r=0 e os segundos até a janela ser reiniciada. Também RATE_LIMITED, para uma sessão do painel de Website Archive: o limite diário de 100 unidades por dia UTC, compartilhado por capturas individuais e em lote (uma unidade por URL), compare, redact e certify-hash (uma unidade por requisição); uma requisição que não cabe é recusada por inteiro (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After e details.retry_after_seconds dão os segundos até o próximo dia UTC, e os cabeçalhos por minuto RateLimit e X-RateLimit-* são omitidos). Uma recusa curta com apenas details.retry_after_seconds e Retry-After: 1 indica uma atualização concorrente do contador: tente de novo. Uma recusa que não é culpa do chamador devolve as unidades; trabalho aceito com 202 que falha depois mantém a sua unidade. Uma barreira branda, não um limite de segurança.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/certify-hash": {
      "post": {
        "tags": [
          "Certificação de hash"
        ],
        "operationId": "certifyHash",
        "summary": "Certificar hashes de arquivos",
        "description": "Envie um SHA-256 (hash) ou até 100 (hashes). O pacote contém manifest.json (com os seus hashes), manifest.sig, publickey.pem, evidence.pdf, os scripts de verification/ e, quando disponível, manifest.json.ots.\n\n- Um hash: síncrono, retorna 200 com o certificado pronto.\n- Vários hashes com combined: true: um certificado que lista todos os hashes, síncrono, retorna 200.\n- Vários hashes sem combined: um certificado independente por hash, iniciado em segundo plano; retorna 202 com os certify_ids. Consulte GET /captures/{certify_id}; com webhook_url, cada um envia certify.completed.\n\nCada certificado conta como uma captura para a cobrança, o limite mensal e o limite de requisições (N hashes sem combined contam N; N maior que o limite por janela retorna 400 RATE_LIMIT_COST_TOO_HIGH). Um `label` é impresso no certificado: é cortado em 200 caracteres e deve ser texto simples (sem caracteres de controle, invisíveis ou de direção de texto; caso contrário, 400). Em um lote, um erro sobre um item o identifica em `error.details.index`.\n\nLimite de requisições por conta em janelas de 60 segundos (todas as chaves de API da conta e a sua sessão do painel compartilham uma janela). Depois que o limitador é executado, as respostas desta rota incluem os cabeçalhos `RateLimit-Policy` e `RateLimit` (baseados no IETF draft-ietf-httpapi-ratelimit-headers), além dos legados `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Um 429 acrescenta `Retry-After`.\n\nEnvie um Idempotency-Key para que uma nova tentativa (por exemplo após um 504) retorne a primeira resposta em vez de uma segunda execução cobrada (24 horas, mesmas regras de POST /captures).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 como 64 caracteres hexadecimais (sem distinção entre maiúsculas e minúsculas). Use hash ou hashes."
                  },
                  "label": {
                    "type": "string",
                    "description": "Nome exibido para o arquivo. Valores mais longos são encurtados para 200 caracteres."
                  },
                  "hashes": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "SHA-256 em hex."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "hash": {
                              "type": "string"
                            },
                            "label": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "hash"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "combined": {
                    "type": "boolean",
                    "description": "Certificar todos os hashes em um único certificado.",
                    "default": false
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "sha256"
                    ],
                    "default": "sha256"
                  },
                  "language": {
                    "type": "string",
                    "description": "Idioma dos PDFs legíveis por pessoas. Um destes: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Maiúsculas, hífen e região são tolerados (\"DE\", \"fr-FR\" e \"PT-BR\" são aceitos); \"pt\" e \"zh\" sozinhos são rejeitados. Um valor não suportado retorna 400 MALFORMED_REQUEST."
                  },
                  "case_reference": {
                    "type": "string",
                    "description": "Impresso no certificado. Segue a regra de texto simples (ver PlainText); no máximo 200 caracteres.",
                    "maxLength": 200
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Como em CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Adiciona um modelo de certificação não assinado (rule_902_certification.pdf, veja CaptureOptions.include_rule_902). Desativado no momento: o modelo de certificação está temporariamente desligado, então este campo é aceito e ignorado (nenhum rule_902_certification.pdf é gerado ou listado no pacote) e a resposta traz o aviso RULE902_DISABLED.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "URL https que recebe certify.completed. A URL deve ser https e não pode apontar para um endereço privado, de loopback, link-local, de NAT de nível de operadora ou de metadados de nuvem (as mesmas regras de uma URL de captura; caso contrário, 422 SSRF_BLOCKED). Um valor vazio ou só com espaços significa sem webhook. Requer uma chave de API: enviado a partir de uma sessão do painel, é recusado com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave.",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "label": "contract.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Certificado pronto (um hash, ou combinado).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CertifyResult"
                }
              }
            }
          },
          "202": {
            "description": "Vários certificados independentes iniciados. Um certificado que o limite mensal ou a franquia do sandbox recusa durante a execução falha: sua linha (GET /captures/{certify_id}) tem status failed e error.code QUOTA_EXCEEDED.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch_id": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "certify_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "IDs dos certificados que foram criados (incluindo um cujo início falhou)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "certificates": {
                      "type": "array",
                      "description": "Uma entrada por hash enviado, na ordem. status failed: aquele certificado não pôde ser iniciado (sua linha, quando chegou a ser criada, é marcada como failed); os demais continuam em execução.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "certify_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "hash_sha256": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "processing",
                              "failed"
                            ]
                          }
                        }
                      }
                    },
                    "failed_count": {
                      "type": "integer",
                      "description": "Quantas entradas de certificates não conseguiram iniciar. Quando todas falham, a resposta passa a ser 500 CONFIGURATION_ERROR."
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason indica o campo, por exemplo \"label must be a string\"; details.index indica o item de um lote), INVALID_HASH (details.index em um lote) ou INVALID_URL (webhook_url que não é https, ou com credenciais). WEBHOOK_REQUIRES_API_KEY: webhook_url enviado a partir de uma sessão do painel (webhooks exigem uma chave de API). RATE_LIMIT_COST_TOO_HIGH: mais hashes (sem combined) do que uma janela do limite de requisições permite; details { max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED, ou QUOTA_EXCEEDED nos caminhos síncronos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY (details.reason \"no_billing_account\" quando uma chave live ou uma sessão funciona como teste por não haver conta de cobrança vinculada). EMAIL_NOT_VERIFIED: uma sessão do dashboard cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria novos trabalhos. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o dashboard). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel do ProofSnap (novos trabalhos nesse plano são criados apenas no painel; uma barreira leve, não uma fronteira de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: webhook_url aponta para um endereço privado, de loopback, link-local ou bloqueado por outro motivo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete o Retry-After. RateLimit informa r=0 e os segundos até a janela ser reiniciada. Também RATE_LIMITED, para uma sessão do painel de Website Archive: o limite diário de 100 unidades por dia UTC, compartilhado por capturas individuais e em lote (uma unidade por URL), compare, redact e certify-hash (uma unidade por requisição); uma requisição que não cabe é recusada por inteiro (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After e details.retry_after_seconds dão os segundos até o próximo dia UTC, e os cabeçalhos por minuto RateLimit e X-RateLimit-* são omitidos). Uma recusa curta com apenas details.retry_after_seconds e Retry-After: 1 indica uma atualização concorrente do contador: tente de novo. Uma recusa que não é culpa do chamador devolve as unidades; trabalho aceito com 202 que falha depois mantém a sua unidade. Uma barreira branda, não um limite de segurança.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: a certificação falhou (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: a certificação síncrona (um hash, ou combinada) não terminou em cerca de 25 s. Ela ainda pode ser concluída: details.certify_id e details.poll_path indicam o registro a consultar com GET /captures/{capture_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/webhook-events/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEvents",
        "summary": "Log de entregas de webhook",
        "description": "Todas as tentativas de entrega de uma captura, certificado ou tarjamento, da mais antiga para a mais recente.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Log de entregas.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "webhook_url": {
                      "type": "string",
                      "description": "A URL de webhook da linha (options.webhook_url de uma captura, ou a URL informada com um certificado ou tarjamento; null quando não há). Para o proprietário do escritório para o qual uma captura foi feita, ao ler o log de um assento, apenas esquema, host, porta e caminho (query e fragmento removidos, pois podem conter um token); essa leitura fica registrada no histórico de acesso da captura (action webhook_events).",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "attempt_number": {
                            "type": "integer"
                          },
                          "attempted_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "status_code": {
                            "type": "integer",
                            "nullable": true
                          },
                          "latency_ms": {
                            "type": "integer",
                            "nullable": true
                          },
                          "success": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": "string",
                            "nullable": true,
                            "description": "null quando entregue. Caso contrário, uma classe genérica, nunca o erro de rede bruto: connection_failed, timeout, blocked_address (o host foi resolvido para um endereço privado ou bloqueado de outra forma no momento da entrega), redirect_not_followed (o endpoint respondeu com 3xx; redirecionamentos nunca são seguidos), http_<status> (uma resposta fora de 2xx), ou uma frase explicando por que o evento não foi enviado (sem chave de API, chave revogada ou não encontrada, sem segredo de webhook, falha ao consultar o segredo)."
                          },
                          "delivery_id": {
                            "type": "string",
                            "description": "O cabeçalho webhook-id desta entrega sem o prefixo `msg_`."
                          }
                        }
                      }
                    },
                    "attempt_count": {
                      "type": "integer"
                    },
                    "last_success": {
                      "type": "string",
                      "description": "attempted_at da última tentativa bem-sucedida.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a captura não é sua.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures": {
      "post": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "createSchedule",
        "summary": "Criar um agendamento",
        "description": "interval e cron: cada execução faz uma captura e, com webhook_url, envia capture.completed ou capture.failed.\n\non_change: cada execução primeiro busca a página (HTTP simples, ou renderização em navegador com js_render) e calcula o hash do seu texto normalizado. Essa verificação não é uma captura e não é cobrada. A primeira execução faz uma captura de referência. Quando o hash muda, uma nova captura é feita, comparada com a anterior, e monitor.change_detected é enviado para webhook_url. Uma página que retorna erro ou quase nenhum texto conta como sem alteração.\n\nApós 5 execuções consecutivas com falha, o agendamento se pausa sozinho. Quando o limite mensal é atingido ou a assinatura está em atraso, as execuções são puladas e o agendamento continua ativo.\n\nLimites por plano:\n\n| Plano | Agendamentos ativos | Cadência mínima | on_change |\n| --- | --- | --- | --- |\n| sandbox | 0 | não disponível | não |\n| developer | 3 | 300 s | sim |\n| startup | 25 | 300 s | sim |\n| growth | 200 | 300 s | sim |\n| custom | ilimitado | 300 s | sim |\n| legal_solo | 3 | 300 s | sim |\n| legal_seats | 3 | 300 s | sim |\n| website_archive | 30 | 86400 s | sim |\n| archive_only | 0 | não disponível | não |\n\nIdempotency-Key: uma nova tentativa com a mesma chave e o mesmo corpo dentro de 24 horas recebe de volta o primeiro 201 (cabeçalho Idempotent-Replay: true) em vez de um segundo agendamento; a mesma chave com outro corpo resulta em 422 IDEMPOTENCY_KEY_REUSED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, INVALID_URL, INVALID_CRON, INTERVAL_TOO_SHORT (details.min_interval_seconds) ou um erro nas opções de captura. WEBHOOK_REQUIRES_API_KEY: webhook_url enviado de uma sessão do painel (webhooks exigem uma chave de API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED (details.max_active, details.active) ou ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não está confirmado. PLAN_ARCHIVE_ONLY: o plano mantém apenas o arquivo e não cria trabalho novo. PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave é somente leitura; use o painel). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel ProofSnap (trabalho novo nesse plano é criado apenas no painel; uma barreira branda, não um limite de segurança).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url ou webhook_url aponta para um endereço privado, de loopback, link-local ou bloqueado por outro motivo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      },
      "get": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "listSchedules",
        "summary": "Listar agendamentos",
        "description": "Até 100 dos seus agendamentos.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Agendamentos.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "getSchedule",
        "summary": "Obter um agendamento",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "O agendamento.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "updateSchedule",
        "summary": "Pausar, retomar ou editar um agendamento",
        "description": "Envie status para pausar ou retomar e/ou qualquer campo de ScheduleRequest para editar. Um campo de nível superior enviado substitui o armazenado, inclusive um null explícito (webhook_url, timezone e js_render são limpos dessa forma); um campo omitido mantém o valor armazenado; o resultado é validado novamente como um todo. `options` é a exceção à edição campo a campo: ele é substituído como um mapa inteiro, não mesclado chave por chave, então envie as options completas que você deseja (leia-as antes com GET). Uma opção omitida do mapa é removida. Retomar verifica o limite de agendamentos ativos, zera a contagem de falhas e calcula a próxima execução a partir de agora.\n\nA partir de uma sessão do painel, não é possível definir webhook_url (400 WEBHOOK_REQUIRES_API_KEY); enviar null ou uma string vazia o limpa. Um agendamento criado a partir de uma sessão do painel nunca teve uma chave de API com a qual assinar, então editá-lo também descarta qualquer webhook_url armazenado nele anteriormente, e uma chave de API também não pode adicionar um a ele (400 WEBHOOK_REQUIRES_API_KEY): o agendador assina os webhooks de um agendamento com o segredo de webhook da chave com a qual o agendamento foi criado. Para receber webhooks, crie o agendamento com uma chave de API.\n\nAlterar url, mode ou js_render de um agendamento on_change (ou mudar um agendamento para on_change ou de on_change para outro modo) limpa sua linha de base (o hash de sondagem armazenado e a última captura), de modo que a próxima execução faz uma nova captura de linha de base em vez de comparar o novo alvo com o antigo. O corpo da requisição deve ser um objeto JSON (caso contrário, 400 MALFORMED_REQUEST).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "interval",
                      "cron",
                      "on_change"
                    ]
                  },
                  "interval_seconds": {
                    "type": "integer"
                  },
                  "cron_expr": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true,
                    "description": "Fuso horário IANA para o cron. null ou uma string vazia volta para UTC."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Envie null ou uma string vazia para removê-la. A URL deve ser https e não pode apontar para um endereço privado, de loopback, link-local, de NAT de nível de operadora ou de metadados de nuvem (as mesmas regras de uma URL de captura; caso contrário, 422 SSRF_BLOCKED). Exige uma chave de API dos dois lados: enviada de uma sessão do painel, ou enviada com uma chave de API para um agendamento criado no painel, é recusada com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave com a qual o agendamento foi criado.",
                    "nullable": true
                  },
                  "js_render": {
                    "type": "boolean",
                    "nullable": true,
                    "description": "null significa false."
                  },
                  "options": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CaptureOptions"
                      }
                    ],
                    "description": "Substitui as opções armazenadas como um todo (sem mesclar chave por chave). Envie o mapa completo; uma opção omitida é removida. null redefine as opções para os padrões. options.webhook_url nunca é armazenado (veja webhook_url)."
                  },
                  "max_capture_age_seconds": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 86400,
                    "maximum": 34560000,
                    "description": "Somente on_change: uma captura completa pelo menos a cada N segundos, mesmo sem mudança (de 1 a 400 dias, mais longo que interval_seconds). null o remove. Mudar o mode para interval ou cron o descarta."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "O agendamento atualizado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (inclusive nada a atualizar), INVALID_URL, INVALID_CRON ou INTERVAL_TOO_SHORT. WEBHOOK_REQUIRES_API_KEY: webhook_url enviado a partir de uma sessão do painel, ou enviado com uma chave de API para um agendamento criado no painel (ele não tem uma chave cujo segredo de webhook possa assinar os webhooks).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED ou ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado (ao retomar um agendamento pausado; pausar e editar continuam liberados). PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive, em qualquer PATCH (inclusive pausar e editar). PLAN_API_READ_ONLY com details.reason \"session_outside_dashboard\": uma sessão do painel de uma conta Website Archive cuja requisição não tem cabeçalho Origin ou tem um que não é um endereço do painel ProofSnap (novos trabalhos nesse plano são criados somente no painel; um bloqueio leve, não uma barreira de segurança). PLAN_ARCHIVE_ONLY: retomar um agendamento no plano Archive only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url ou webhook_url aponta para um endereço privado, de loopback, link-local ou bloqueado por outro motivo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "deleteSchedule",
        "summary": "Excluir um agendamento",
        "description": "Interrompe as próximas execuções. As capturas já feitas são mantidas.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Excluído.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "PLAN_API_READ_ONLY: uma chave de API de uma conta Website Archive (a chave dela é somente leitura; use o painel).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}/runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Capturas automáticas"
        ],
        "operationId": "listScheduleRuns",
        "summary": "Listar execuções de um agendamento",
        "description": "As 50 capturas mais recentes feitas por este agendamento (verificações on_change que não encontraram alteração não são listadas).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Execuções.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "runs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "file_count": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters": {
      "get": {
        "tags": [
          "Casos"
        ],
        "operationId": "listMatters",
        "summary": "Listar casos",
        "description": "Até 200 casos dos quais você é membro. Casos de um escritório do qual você saiu não são listados.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Casos.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matters": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Matter"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Casos"
        ],
        "operationId": "createMatter",
        "summary": "Criar um caso",
        "description": "Você se torna o proprietário. O log de custódia começa com uma entrada matter.created. Os campos de texto são aparados e encurtados para 200 caracteres.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples: os espaços em branco são condensados e removidos das extremidades, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). Obrigatório. Exibido na página pública de verificação do caso."
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples: espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). Vazio ou null significa não definido (armazenado como null). Um valor que não seja string é recusado.",
                    "nullable": true
                  },
                  "case_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples: espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). Vazio ou null significa não definido (armazenado como null). Um valor que não seja string é recusado. Exibido na página pública de verificação do caso.",
                    "nullable": true
                  },
                  "opposing_party": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples: espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). Vazio ou null significa não definido (armazenado como null). Um valor que não seja string é recusado.",
                    "nullable": true
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "Texto livre. Texto simples: espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). Vazio ou null significa não definido (armazenado como null). Um valor que não seja string é recusado. Exibido na página pública de verificação do caso.",
                    "maxLength": 200,
                    "nullable": true
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Smith v. Jones",
                "case_number": "2026-CV-0142"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MISSING_FIELD: name ausente ou vazio. MALFORMED_REQUEST: JSON inválido. MALFORMED_REQUEST: um campo de texto que não é uma string, tem mais de 200 caracteres ou contém caracteres de controle ou de direção de texto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "get": {
        "tags": [
          "Casos"
        ],
        "operationId": "getMatter",
        "summary": "Obter um caso",
        "description": "O caso com suas capturas (até 500), seu log de custódia e até 100 eventos de acesso. `members` informa o e-mail de cada entrada de `member_uids` e `actor_emails` mapeia os uids citados no log de custódia e no audit_stream deste caso (atores e o alvo de `member.added` / `member.removed`, portanto também membros removidos) para o respectivo e-mail; um e-mail é null, ou o uid não aparece no mapa, quando não pode ser lido. Somente chamadores que podem abrir o caso recebem esses campos; `/matter-verify` nunca traz e-mails. Um chamador que pode revogar o link público de compartilhamento (o proprietário do caso, o proprietário de um escritório encerrado ou arquivado) também recebe `share`: `{ share_url, created_at, expires_at }` do link ativo (`expires_at` é sempre null: os links funcionam até serem revogados), ou null quando não há nenhum; os outros membros nunca recebem o campo. As entradas de audit_stream trazem source_ip e user_agent somente para o proprietário do caso; os membros recebem as entradas sem esses dois campos.\n\nUm caso de um escritório encerrado traz `read_only: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "O caso.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Matter"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "members": {
                          "type": "array",
                          "description": "Uma entrada por entrada de `member_uids`, na mesma ordem.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uid": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "nullable": true,
                                "description": "Null quando não é possível ler a conta."
                              }
                            }
                          }
                        },
                        "actor_emails": {
                          "type": "object",
                          "description": "`{ uid: email }` para as pessoas citadas no custody_log e no audit_stream deste caso (excluído o ator do sistema). Um uid cujo e-mail não pode ser lido fica ausente.",
                          "additionalProperties": {
                            "type": "string",
                            "format": "email"
                          }
                        },
                        "captures": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MatterCapture"
                          }
                        },
                        "custody_log": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustodyEntry"
                          }
                        },
                        "audit_stream": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AuditEntry"
                          }
                        },
                        "share": {
                          "type": "object",
                          "nullable": true,
                          "description": "Somente para um chamador que possa revogar o link. Null quando não existe link ativo.",
                          "properties": {
                            "share_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "Sempre null: links de compartilhamento não expiram."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Casos"
        ],
        "operationId": "updateMatter",
        "summary": "Atualizar um caso",
        "description": "Somente o proprietário. Os campos de texto têm os espaços das extremidades removidos e são encurtados para 200 caracteres. name é obrigatório: quando enviado, deve ser uma string não vazia, e um name vazio ou só com espaços, null ou que não seja string é recusado com 400 e nada é alterado. Membros: add_member_email adiciona uma pessoa que já tem conta ProofSnap e pertence ao escritório do caso; sem conta, endereço não confirmado e conta fora do escritório recebem o mesmo 404 NOT_FOUND; remove_member_uid remove um membro (não o proprietário). Quando ambos são enviados, apenas add_member_email é aplicado. Um caso tem no máximo 50 membros (um caso cheio recusa antes de o endereço ser consultado). Alterações de membros são gravadas no registro de custódia. Uma alteração de name, client, case_number, opposing_party, jurisdiction ou status é gravada no registro de custódia como matter.updated; o target dela é uma string JSON com fields (os nomes dos campos alterados) e, quando o status mudou, o novo status. Valores de texto livre não são gravados no registro de custódia público e com hash: os valores antigo e novo vão para o log de acesso (audit_stream, entrada matter.updated). Em um caso arquivado, uma renomeação ou add_member_email é recusada com 409 MATTER_ARCHIVED, a menos que a mesma requisição o reabra (status \"open\"); remove_member_uid e a reabertura continuam funcionando. Cada convite conta para um limite diário de 50 por proprietário que convida (convites ao escritório pelo painel e add_member_email de casos juntos, recusas incluídas, já que cada um consulta um endereço); no limite, a requisição retorna 429 RATE_LIMITED. Uma falha do lado do servidor não conta.\n\nRetenção legal: `{ \"legal_hold\": true|false }` sozinho define a retenção legal do caso (somente o proprietário do caso) e a aplica às capturas dele na mesma requisição, com os efeitos de uma retenção de captura (legal hold do S3 Object Lock, entradas de custódia capture.legal_hold_placed / capture.legal_hold_released, a cadeia de eventos da captura). A resposta é o caso mais `changed` (o flag mudou), `captures_held` ou `captures_released`, `skipped` ([{ capture_id, code }], por exemplo uma captura na lixeira) e `truncated` (mais de 1000 capturas: envie o mesmo valor de novo para continuar). Capturas atribuídas ao caso depois, enquanto a retenção estiver ativa, ficam retidas no momento em que são atribuídas. Uma unidade do limite de requisições por requisição, mais uma a cada 10 capturas alteradas.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Deve conter pelo menos um caractere que não seja espaço em branco. Omita para manter o nome atual. Mesma regra de texto simples: espaços em branco agrupados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto ou invisíveis (U+200C e U+200D são permitidos).",
                    "maxLength": 200
                  },
                  "client": {
                    "type": "string",
                    "nullable": true,
                    "description": "Texto simples: os espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). null ou uma string só com espaços em branco limpa o campo (armazenado como null). Um valor que não seja string é recusado com 400.",
                    "maxLength": 200
                  },
                  "case_number": {
                    "type": "string",
                    "nullable": true,
                    "description": "Texto simples: os espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). null ou uma string só com espaços em branco limpa o campo (armazenado como null). Um valor que não seja string é recusado com 400.",
                    "maxLength": 200
                  },
                  "opposing_party": {
                    "type": "string",
                    "nullable": true,
                    "description": "Texto simples: os espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). null ou uma string só com espaços em branco limpa o campo (armazenado como null). Um valor que não seja string é recusado com 400.",
                    "maxLength": 200
                  },
                  "jurisdiction": {
                    "type": "string",
                    "nullable": true,
                    "description": "Texto simples: os espaços em branco são condensados e aparados, no máximo 200 caracteres, sem caracteres de controle, de direção de texto (bidi) ou invisíveis (os joiners U+200C e U+200D, necessários na grafia do hindi e do persa, são permitidos) (caso contrário, 400 MALFORMED_REQUEST). null ou uma string só com espaços em branco limpa o campo (armazenado como null). Um valor que não seja string é recusado com 400.",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "description": "Use POST /matters/{matter_id}/seal para selar.",
                    "enum": [
                      "open",
                      "archived"
                    ]
                  },
                  "add_member_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "remove_member_uid": {
                    "type": "string"
                  },
                  "legal_hold": {
                    "type": "boolean",
                    "description": "Retenção legal do caso, enviada sozinha (combinada com outro campo: 400). true retém todas as capturas atuais do caso e todas as capturas atribuídas a ele enquanto a retenção estiver ativa (legal_hold_source \"matter:<id>\"; uma captura já retida mantém a sua própria retenção). false libera apenas as retenções que o caso aplicou; uma retenção manual ou em lote permanece. Somente o proprietário do caso. Não é bloqueada por uma assinatura em atraso. Aplicar exige um plano pago (403 LEGAL_HOLD_NOT_AVAILABLE, details.reason plan) e capturas em modo de teste são ignoradas (código LEGAL_HOLD_NOT_AVAILABLE em `skipped`); false sozinho é aceito também depois do fim do plano Legal."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "O caso atualizado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: name vazio, só com espaços em branco ou que não é uma string; um campo de texto que não é uma string, tem mais de 200 caracteres ou contém caracteres de controle ou de direção de texto; status inválido; nada a atualizar; já é membro; ou remoção do proprietário.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (não é o proprietário), LEGAL_HOLD_NOT_AVAILABLE (legal_hold true sem um plano pago) ou EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND, ou NOT_FOUND (nenhuma conta elegível para o e-mail: inexistente, não confirmada ou fora do escritório, uma única resposta para todos os casos; ou remove_member_uid não é membro).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED ou MATTER_ARCHIVED (renomeação ou novo membro em um caso arquivado). COMPANY_READ_ONLY: o caso pertence a um escritório que foi encerrado ou é mantido como company archive (chamador em um plano Legal; abrir, exportar e revogar o compartilhamento continuam funcionando).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: foi atingido o limite diário de 50 convites de membros por proprietário que convida (convites do escritório pelo painel e add_member_email de casos contam juntos; details.daily_limit, details.action \"member_invite\"). Retry-After indica quando termina o dia UTC.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/captures": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Casos"
        ],
        "operationId": "assignMatterCaptures",
        "summary": "Adicionar ou remover capturas",
        "description": "Qualquer membro pode chamar isto, mas só são movidas as capturas criadas por quem chama; as demais são retornadas em skipped, assim como capturas que já estão em outro caso e capturas que já estão (ou, com unassign, que não estão) neste caso. Entradas que não são IDs de captura são ignoradas e no máximo 100 são processadas. Cada alteração é registrada no log de custódia. Um caso arquivado não aceita novas capturas (409 MATTER_ARCHIVED); unassign continua funcionando.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "unassign": {
                    "type": "boolean",
                    "description": "true remove as capturas do caso.",
                    "default": false
                  }
                },
                "required": [
                  "capture_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "assigned": {
                      "type": "integer",
                      "description": "Número de capturas alteradas."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "unassign": {
                      "type": "boolean"
                    },
                    "legal_holds_placed": {
                      "type": "integer",
                      "description": "Presente quando a retenção legal do caso está ativa: em quantas das capturas atribuídas ela aplicou retenção."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: nenhum ID de captura válido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, ou EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED, ou MATTER_ARCHIVED (adição a um caso arquivado). COMPANY_READ_ONLY: o caso pertence a um escritório que foi encerrado ou é mantido como Company archive (chamador em um plano Legal; abrir, exportar e revogar o compartilhamento continuam funcionando).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/seal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Casos"
        ],
        "operationId": "sealMatter",
        "summary": "Selar um caso",
        "description": "Somente o proprietário. Ancora o hash atual do topo da cadeia de custódia com OpenTimestamps e, quando a implantação tem eIDAS configurado e o chamador é live (uma chave de API live, ou uma sessão do painel com cobrança vinculada), com um carimbo do tempo eIDAS (o carimbo do tempo eIDAS do selamento não é cobrado; se falhar, o selamento prossegue sem ele). Um chamador de teste (chave de API de teste ou sessão de sandbox) é selado apenas com OpenTimestamps e o selamento registra eidas_status not_obtained com eidas_reason test_key; a requisição não aceita opção de eIDAS, então isso não é um erro. O caso passa então a ser somente leitura: sem edições, sem alterações de capturas, e as suas capturas não podem ser excluídas. Um caso que ainda contém uma captura na lixeira não é selado (409 MATTER_HAS_TRASHED_CAPTURES): restaure a captura ou remova-a do caso antes.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Selado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "sealed"
                      ]
                    },
                    "seal": {
                      "$ref": "#/components/schemas/MatterSeal"
                    },
                    "custody_head_hash": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: o caso não tem custody head.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal. FORBIDDEN: não é o proprietário. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED (já selado), MATTER_CHANGED (o log de custódia mudou durante a selagem; tente novamente) ou MATTER_HAS_TRASHED_CAPTURES (uma captura do caso está na lixeira; restaure-a ou remova-a do caso primeiro). COMPANY_READ_ONLY: o caso pertence a um escritório que foi encerrado ou é mantido como company archive (chamador em um plano Legal; abrir, exportar e revogar o compartilhamento continuam funcionando).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "TIMESTAMP_SERVICE_UNAVAILABLE (a ancoragem falhou, nada foi alterado; tente de novo) ou CONFIGURATION_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "EIDAS_SEAL_RETRY: o carimbo do tempo eIDAS do selo não chegou a tempo (ou ainda está sendo emitido). Nada foi selado. Tente novamente após Retry-After segundos: a nova tentativa recebe o carimbo do tempo já emitido para este head de custódia, sem um segundo carimbo. SERVICE_UNAVAILABLE: o gateway não conseguiu verificar a credencial; tente novamente com backoff.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "EIDAS_SEAL_RETRY",
                    "message": "The eIDAS timestamp of the seal did not arrive in time. Nothing was sealed. Retry after the Retry-After interval; the retry reuses the timestamp already issued for this custody head.",
                    "details": {
                      "reason": "The eIDAS timestamp did not arrive in time. Nothing was sealed. Retry the seal.",
                      "retry_after_seconds": 15
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "/matters/{matter_id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Casos"
        ],
        "operationId": "exportMatter",
        "summary": "Exportar um caso",
        "description": "Somente o proprietário. Gera um ZIP com custody_report.pdf, custody_log.json, matter.json, README.txt, access_log.json (quando há eventos de acesso) e o pacote de provas de cada captura. Com a numeração Bates ativada (o padrão), adiciona cópias de cada evidence.pdf numeradas com Bates em exhibits/ (anexos numerados a partir da prova mais antiga) e, com formats.loadfile, um load file Concordance/Opticon. Com formats.pdfa, as cópias para protocolo também são convertidas em PDF/A quando o conversor está disponível (em uma exportação síncrona, apenas dentro de um orçamento de tempo); somente as cópias contadas em pdfa_count são PDF/A, as demais são PDFs que não alegam ser PDF/A, e o README.txt menciona PDF/A apenas quando pdfa_count é maior que 0.\n\nUm caso com até 20 capturas é exportado de forma síncrona (200). Um caso maior é exportado em segundo plano: a resposta é 202 com um job_id; consulte GET /matters/{matter_id}/export?job_id=. Capturas que não cabem no limite de tamanho (120 MB no modo síncrono; 800 MB e 300 capturas em segundo plano) são listadas nos relatórios, mas não incluídas no ZIP.\n\nFunciona em casos abertos e selados. Um caso arquivado é recusado com 409 MATTER_ARCHIVED (reabra-o primeiro), exceto para um chamador que não pode reabri-lo: o proprietário de um escritório encerrado dentro do período de retenção e uma conta Archive only. Cada exportação é registrada como export.generated no log de acesso. Cada exportação conta para um limite diário de 50 por usuário; uma exportação em segundo plano que falha devolve a sua unidade.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bates": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      },
                      "prefix": {
                        "type": "string",
                        "description": "Apenas letras e dígitos, em maiúsculas, até 12. Padrão: a partir do número do processo ou do nome do caso."
                      },
                      "start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "exhibit_start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "pad": {
                        "type": "integer",
                        "minimum": 4,
                        "maximum": 8,
                        "default": 6
                      }
                    }
                  },
                  "formats": {
                    "type": "object",
                    "properties": {
                      "loadfile": {
                        "type": "boolean",
                        "description": "Inclui o load file de e-discovery (requer numeração Bates).",
                        "default": true
                      },
                      "pdfa": {
                        "type": "boolean",
                        "description": "Converte em PDF/A as cópias para protocolo com numeração Bates (requer numeração Bates). Melhor esforço: uma cópia que não for convertida continua sendo um PDF sem declaração de PDF/A; pdfa_count na resposta informa quantas foram convertidas.",
                        "default": true
                      }
                    }
                  },
                  "export_locale": {
                    "type": "string",
                    "description": "Idioma do custody_report.pdf, do índice de anexos e do README.txt: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk (um hífen é aceito no lugar do sublinhado). Outros valores usam inglês como padrão."
                  },
                  "declaration": {
                    "type": "object",
                    "description": "Aceito por compatibilidade. Seus campos de texto (name, title, party, case_caption, executed_location) seguem a regra de texto simples (veja PlainText): um valor com caractere proibido é recusado com 400 MALFORMED_REQUEST. Valores longos demais são encurtados (name, title, executed_location 120, party 160, case_caption 200 caracteres)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exportação pronta.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "Link de download pré-assinado, válido por 24 horas.",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "capture_count": {
                      "type": "integer"
                    },
                    "captures_included": {
                      "type": "integer"
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "description": "Capturas listadas nos relatórios, mas não incluídas no pacote porque um limite de tamanho foi atingido."
                    },
                    "custody_events": {
                      "type": "integer"
                    },
                    "exhibits_count": {
                      "type": "integer",
                      "description": "Cópias de anexos com numeração Bates."
                    },
                    "bates_prefix": {
                      "type": "string",
                      "nullable": true
                    },
                    "searchable": {
                      "type": "boolean",
                      "description": "Sempre false: nenhuma camada de texto é adicionada às cópias para protocolo. Mantido para uma estrutura de resposta estável."
                    },
                    "pdfa_count": {
                      "type": "integer",
                      "description": "Cópias de protocolo em exhibits/ que são PDF/A. 0 significa que nenhuma cópia é PDF/A e nenhuma alega ser."
                    },
                    "loadfile": {
                      "type": "boolean"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "generated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Exportação iniciada em segundo plano.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "async": {
                      "type": "boolean"
                    },
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "description": "Inclui o stage /v1."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: uma opção de exportação é inválida (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal. FORBIDDEN: não é o proprietário. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: reabra o caso (PATCH status \"open\") primeiro.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: não foi possível gerar ou iniciar a exportação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: a exportação síncrona não terminou em cerca de 25 s (details.reason). Tente novamente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Casos"
        ],
        "operationId": "getMatterExport",
        "summary": "Consultar uma exportação em segundo plano",
        "description": "Somente o proprietário. Um job ainda em processamento 30 minutos depois de iniciado é informado como failed com o erro \"export timed out\". Um job termina uma única vez: um job informado como failed nunca passa a ready depois, e um job ready nunca é informado como failed.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status do job. Quando estiver pronto, url e as contagens são preenchidas.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed"
                      ]
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    },
                    "captures_included": {
                      "type": "integer",
                      "nullable": true
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "nullable": true
                    },
                    "custody_events": {
                      "type": "integer",
                      "nullable": true
                    },
                    "size_bytes": {
                      "type": "integer",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: job_id ausente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal. FORBIDDEN: não é o proprietário.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND, ou NOT_FOUND para um job desconhecido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/share": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Casos"
        ],
        "operationId": "shareMatter",
        "summary": "Criar um link público de verificação",
        "description": "Somente o proprietário. Retorna um link que qualquer pessoa pode abrir para ver o nome, o número do processo, a jurisdição, o status, o log de custódia e o selo do caso (veja GET /matter-verify). Nenhum conteúdo de captura é compartilhado. Chamar de novo retorna o mesmo link. O link funciona até você revogá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Link de compartilhamento.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "share_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: o plano não é um plano Legal. FORBIDDEN: não é o proprietário. EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: um caso arquivado não é compartilhado. COMPANY_READ_ONLY: o caso pertence a um escritório que foi encerrado ou é mantido como Company archive (chamador em um plano Legal; abrir, exportar e revogar compartilhamentos continuam funcionando).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Casos"
        ],
        "operationId": "revokeMatterShare",
        "summary": "Revogar o link público",
        "description": "Somente o proprietário. O link deixa de funcionar. Um POST posterior cria um novo. O proprietário de um escritório encerrado (ou mantido como company archive) e o proprietário de um caso no plano Archive only podem revogar o link sem um plano Legal (criar um link continua sendo um recurso do plano Legal).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revogado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: chamador live cuja assinatura (ou escritório) está em atraso ou não paga."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (não é o proprietário), ou EMAIL_NOT_VERIFIED: uma sessão do painel cujo endereço de e-mail não foi confirmado, ou uma chave de API de uma conta nessa situação.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "health",
        "summary": "Verificação de status",
        "description": "Sempre 200 enquanto a API estiver respondendo. Com deep=1 (ou true), a resposta adiciona dependencies: se cada serviço downstream está configurado e, para o eIDAS, uma sondagem ao vivo do serviço de carimbo do tempo. Somente o eIDAS é sondado; os demais informam apenas a configuração. status passa a degraded quando o eIDAS está configurado e sua sondagem falha. Uma sondagem que não responde a tempo é informada em dependencies (probe_timeout, ou eidas.error timeout), nunca como falha de /health. deep=1 tem limite de requisições por IP do cliente (10 por minuto): acima disso, a requisição retorna 429 RATE_LIMITED com Retry-After.",
        "security": [],
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status de saúde.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "stage": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "uptime_ms": {
                      "type": "integer",
                      "description": "Milissegundos desde que a instância que atende foi iniciada."
                    },
                    "version": {
                      "type": "string"
                    },
                    "dependencies": {
                      "type": "object",
                      "properties": {
                        "eidas": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            },
                            "healthy": {
                              "type": "boolean",
                              "description": "null quando não configurado.",
                              "nullable": true
                            },
                            "error": {
                              "type": "string",
                              "description": "Código fixo quando a verificação do eIDAS não indicou estado saudável: timeout (sem resposta em 15 s), unavailable (o serviço de carimbo do tempo respondeu que não está saudável), error (não foi possível executar a verificação). Os detalhes são registrados no servidor, nunca retornados.",
                              "enum": [
                                "timeout",
                                "unavailable",
                                "error"
                              ]
                            }
                          }
                        },
                        "ots": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "c2pa_sign": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "ntp": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "whois": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "error": {
                          "type": "string",
                          "description": "Apenas quando a própria verificação de dependências falhou: probe_timeout quando ela não respondeu em 20 s, probe_failed nos demais casos. A resposta continua sendo 200."
                        }
                      },
                      "description": "Somente com deep=1."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ledger/{hash}": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "ledgerLookup",
        "summary": "Consultar um hash de manifesto",
        "description": "Verifica se o ProofSnap registrou a emissão de um pacote cujo manifest.json tem este SHA-256. Capturas, certificados, comparações e tarjamentos são registrados quando emitidos, com o hash do manifesto que é entregue (após a etapa do carimbo do tempo qualificado); pacotes emitidos antes da existência do registro não constam nele. issued: false significa apenas que não há registro.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "SHA-256 de manifest.json, 64 caracteres hexadecimais, maiúsculas ou minúsculas, opcionalmente com o prefixo sha256:.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado da consulta.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "manifest_sha256": {
                      "type": "string"
                    },
                    "issued": {
                      "type": "boolean"
                    },
                    "issued_at": {
                      "type": "string",
                      "nullable": true,
                      "format": "date-time",
                      "description": "Ausente quando issued é false."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "capture",
                        "certify",
                        "compare",
                        "redaction"
                      ],
                      "nullable": true,
                      "description": "Ausente quando issued é false."
                    },
                    "eidas": {
                      "type": "boolean",
                      "nullable": true,
                      "description": "Se o pacote emitido traz um carimbo do tempo qualificado eIDAS (manifest.json.tsr). Ausente quando issued é false; null para registros gravados antes de 2026-10-01."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: o hash não tem 64 caracteres hexadecimais.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matter-verify": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "matterVerify",
        "summary": "Visualização pública de um caso compartilhado",
        "description": "Dados somente leitura por trás de um link de compartilhamento do caso: detalhes do caso, o registro de custódia (para que a cadeia de hashes possa ser recalculada) e o selo com a sua prova OpenTimestamps e o token eIDAS. Nenhum conteúdo de captura, link de download ou log de acesso. attested_by.organization é o nome, definido pelo proprietário, do escritório ao qual o caso pertence (um caso de escritório), ou null; um caso pessoal não nomeia nenhuma organização. custody_log[].actor_org é o escritório ao qual o ator pertencia quando aquela entrada foi gravada (registrado com a entrada, fora do payload com hash), apenas em um caso de escritório; entradas gravadas antes de isso ser registrado mostram null. Limite de requisições por IP do cliente (30 requisições a cada 60 segundos por padrão).",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Visualização do caso.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "case_number": {
                          "type": "string",
                          "nullable": true
                        },
                        "jurisdiction": {
                          "type": "string",
                          "nullable": true
                        },
                        "status": {
                          "type": "string"
                        },
                        "sealed": {
                          "type": "boolean"
                        },
                        "attested_by": {
                          "type": "object",
                          "properties": {
                            "organization": {
                              "type": "string",
                              "nullable": true
                            }
                          }
                        },
                        "capture_count": {
                          "type": "integer"
                        },
                        "custody_count": {
                          "type": "integer"
                        },
                        "custody_head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "seal": {
                          "type": "object",
                          "properties": {
                            "root_hash": {
                              "type": "string"
                            },
                            "ots_present": {
                              "type": "boolean"
                            },
                            "eidas_present": {
                              "type": "boolean"
                            },
                            "ots_hex": {
                              "type": "string",
                              "nullable": true
                            },
                            "ots_pending": {
                              "type": "boolean"
                            },
                            "tsr_base64": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_serial": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_issued_at": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_ltv": {
                              "type": "boolean"
                            },
                            "sealed_at": {
                              "type": "string"
                            },
                            "eidas_status": {
                              "type": "string",
                              "enum": [
                                "confirmed",
                                "not_obtained",
                                "not_configured"
                              ],
                              "nullable": true,
                              "description": "null em selos feitos antes de 2026-10-01 sem token eIDAS."
                            },
                            "eidas_reason": {
                              "type": "string",
                              "enum": [
                                "token_refused",
                                "unavailable",
                                "test_key",
                                "account_limit"
                              ],
                              "description": "Somente quando eidas_status é not_obtained. test_key: o selo foi feito por um chamador de teste, portanto nenhum carimbo do tempo eIDAS foi solicitado. account_limit: a conta ainda não tem nenhuma fatura paga e atingiu o seu limite eIDAS (20 por dia UTC, 50 por mês UTC), portanto nenhum carimbo do tempo eIDAS foi solicitado."
                            },
                            "eidas_verification_status": {
                              "type": "string",
                              "nullable": true,
                              "description": "verified quando o token passou na verificação do ProofSnap no momento da selagem."
                            },
                            "eidas_revocation_status": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "good",
                                "revoked_after_gen_time",
                                "unknown",
                                "not_checked",
                                null
                              ],
                              "description": "Status do certificado da TSA determinado no momento da selagem (não consultado em tempo real)."
                            },
                            "eidas_validation": {
                              "type": "object",
                              "nullable": true,
                              "additionalProperties": true,
                              "description": "O conteúdo de eidas_validation.json registrado no selamento (cadeia da TSA, raiz fixada, resposta OCSP, CRL, registro de verificação) para que o token em tsr_base64 possa ser verificado offline. null em selamentos mais antigos."
                            }
                          },
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string"
                        }
                      }
                    },
                    "custody_log": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CustodyEntry"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "actor_org": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        ]
                      }
                    },
                    "issuer": {
                      "type": "string",
                      "enum": [
                        "ProofSnap"
                      ]
                    },
                    "verified_at": {
                      "type": "string",
                      "nullable": true,
                      "description": "Sempre null."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: token inválido, desconhecido ou revogado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/captures/export-jobs": {
      "post": {
        "tags": [
          "Capturas"
        ],
        "operationId": "startCaptureExportJob",
        "summary": "Exportar todas as capturas como um único download (job assíncrono)",
        "description": "Inicia um job que reúne todas as suas capturas em um único download, dividido em partes de no máximo cerca de 2 GB. Cada parte é um ZIP simples: README.txt (em inglês) mais README.<language>.txt (o mesmo texto traduzido, quando language não é en; ambos em UTF-8 com byte order mark e quebras de linha CRLF; a linha 1 de cada um aponta para o outro), captures.csv (UTF-8 com byte order mark, CRLF; todas as capturas da exportação com as colunas member (o e-mail do autor em uma exportação do escritório, vazio na sua própria exportação), capture_id, created_at (quando a requisição foi recebida), captured_at (o momento da captura; vazio para uma captura com falha), url, status (completed, failed ou unavailable), export_part, file, ots_file (captures/<capture_id>.upgraded.ots quando aquela parte contém a prova, caso contrário vazio), test_mode (true para uma captura feita com chave de teste: com marca d'água, não é prova) e note (por que um pacote não está incluído, também para cada linha unavailable); a mesma lista em todas as partes, e quando as partes divergem vale a lista da parte de número mais alto) e captures/<capture_id>.zip, o pacote de provas selado original byte a byte, mais captures/<capture_id>.upgraded.ots quando há uma prova OpenTimestamps atualizada armazenada. Cada pacote interno é verificado por si só com seu verification/verify.sh ou verify.ps1. Um pacote maior que 2 GB não é incluído (listado como unavailable com uma nota; baixe-o separadamente com GET /captures/{capture_id}). Capturas com falha aparecem apenas em captures.csv; capturas na lixeira e apagadas não são exportadas. As entradas de cada parte são datadas com o horário da exportação (UTC, também no campo de timestamp estendido). Consulte GET /captures/export-jobs/{job_id} até que status seja ready. **Reutilização:** quando sua última exportação está pronta, tem o mesmo scope (e org) e language, ainda lhe resta pelo menos um dia de janela de download, seus direitos não mudaram e nenhuma captura foi adicionada, concluída, enviada para a lixeira, restaurada ou apagada desde que ela foi planejada, a requisição retorna 200 com esse job (reused: true): nada é reconstruído e nenhuma unidade diária é usada. Uma exportação pronta mais recente com o mesmo scope substitui a anterior (o job anterior passa a expired, expired_reason superseded). Quem pode iniciá-la: uma chave de API live ou a sessão do painel, em todos os planos, incluindo sandbox, Archive only e chaves somente leitura (Website Archive). Uma chave de teste recebe 403 FORBIDDEN (a exportação contém capturas live), e GET /captures/export-jobs/{job_id} retorna 404 EXPORT_JOB_NOT_FOUND para uma chave de teste em qualquer job. Sua própria exportação (scope own) contém suas capturas pessoais e as que você fez para seu escritório atual ou para um escritório do qual você é proprietário, também por meio de uma chave enquanto esse escritório for um Company archive em andamento (as capturas dos outros assentos de um archive só podem ser acessadas pela sessão do painel). scope company (somente o proprietário do escritório, também para um escritório encerrado dentro da sua janela de download; um Company archive em andamento somente pela sessão do painel) exporta as capturas de todos os assentos feitas para o escritório, mais as suas, com o e-mail do autor na coluna member. Um job em execução por conta (409 EXPORT_JOB_RUNNING o identifica), 5 jobs iniciados por conta e por dia UTC (um job com falha devolve sua unidade, um reutilizado não usa nenhuma), e o limite de requisições por minuto do seu plano. No máximo as 20.000 capturas mais recentes por job (truncated).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "own",
                      "company"
                    ],
                    "default": "own"
                  },
                  "org": {
                    "type": "string",
                    "description": "Com scope company: um dos escritórios de que você é proprietário (atual ou último), quando você é proprietário de ambos."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Idioma da tradução do README: README.<language>.txt (a tradução completa) ao lado do README.txt em inglês em todas as partes; en gera apenas o README.txt. Um de: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Maiúsculas/minúsculas, hífen e região são tolerados (\"PT-BR\" é pt_BR); \"pt\" e \"zh\" isolados e qualquer outro valor resultam em 400 MALFORMED_REQUEST. Uma exportação pronta é reutilizada apenas para o mesmo idioma. O painel envia o idioma da sua interface."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A sua exportação pronta ainda está atual e é reutilizada: nenhum job novo, nenhuma unidade diária. Leia os links dela com GET poll_path.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path",
                    "reused"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ready"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "reused": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "O job foi iniciado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: o corpo não é um objeto JSON, ou scope / org é inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a requisição foi feita com uma chave de teste, ou com scope company por alguém que não é o proprietário de um escritório dentro da janela de download dele.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "EXPORT_JOB_RUNNING: um job da conta ainda está em processamento; details.job_id e details.poll_path o identificam.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/export-jobs/{job_id}": {
      "get": {
        "tags": [
          "Capturas"
        ],
        "operationId": "getCaptureExportJob",
        "summary": "Status e links de download de um job de exportação de capturas",
        "description": "O status e o progresso do job; quando ready, um link pré-assinado por parte. Um link é válido até url_expires_at: no máximo 24 horas, nunca depois de download_expires_at e nunca depois das credenciais que o assinaram (pode ser menos de uma hora); consulte novamente depois de url_expires_at para obter links novos. download_expires_at é 7 dias após a meia-noite UTC anterior ao início da primeira parte do job (6 a 7 dias depois de o job ficar ready); as partes são excluídas depois disso. O job perde o download antecipadamente (status expired, partes excluídas, expired_reason) quando uma exportação mais recente do mesmo escopo o substituiu, uma captura que ele contém foi apagada, os dados do seu escritório foram excluídos ou seus direitos não o cobrem mais (verificado a cada leitura de um job ready). Um job que não informa progresso por 30 minutos é encerrado como failed por esta chamada e o que ele gravou é excluído. A primeira resposta com links em uma hora é registrada no log de auditoria (capture.export_job.links_issued). Somente a conta que iniciou o job o vê; uma exportação de escritório que contém um Company archive, somente pela sessão do painel.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cexp_[A-Za-z0-9]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "O job.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "parts"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1",
                      "pattern": "^cexp_[A-Za-z0-9]{16}$"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed",
                        "expired"
                      ],
                      "description": "processing enquanto o plano e as partes são gravados; ready com os links de download; failed (inicie um novo job, a unidade do dia foi devolvida); expired quando a janela de download terminou ou o download foi retirado (expired_reason; as partes são excluídas)."
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "language": {
                      "type": "string",
                      "enum": [
                        "en",
                        "de",
                        "it",
                        "fr",
                        "es",
                        "pt_BR",
                        "cs",
                        "el",
                        "hi",
                        "hu",
                        "id",
                        "ja",
                        "ko",
                        "nl",
                        "pl",
                        "ro",
                        "ru",
                        "sv",
                        "tr",
                        "vi",
                        "zh_CN",
                        "zh_TW",
                        "ar",
                        "he",
                        "da",
                        "fi",
                        "nb",
                        "sk",
                        "uk"
                      ],
                      "description": "O idioma do README do job (README.<language>.txt além de README.txt; en apenas para README.txt)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "ended_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "progress": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "captures_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "captures_done": {
                          "type": "integer"
                        },
                        "parts_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "parts_done": {
                          "type": "integer"
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true quando a conta tem mais de 20.000 capturas exportáveis: o job contém as 20.000 mais recentes."
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true,
                      "description": "Definido quando o job está pronto.",
                      "properties": {
                        "captures": {
                          "type": "integer",
                          "description": "Linhas de captures.csv."
                        },
                        "packaged": {
                          "type": "integer",
                          "description": "Pacotes nas partes."
                        },
                        "failed": {
                          "type": "integer",
                          "description": "Capturas com falha (sem pacote, listadas apenas em captures.csv)."
                        },
                        "unavailable": {
                          "type": "integer",
                          "description": "Pacotes não incluídos: não puderam ser lidos durante a exportação ou têm mais de 2 GB (captures.csv indica quais)."
                        }
                      }
                    },
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "part",
                          "bytes",
                          "count",
                          "download_url"
                        ],
                        "properties": {
                          "part": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 1,
                            "description": "Número da parte, de 1 até o número de partes."
                          },
                          "bytes": {
                            "type": "integer",
                            "example": 1834201112,
                            "description": "Tamanho do ZIP da parte em bytes."
                          },
                          "count": {
                            "type": "integer",
                            "example": 412,
                            "description": "Pacotes de captura nesta parte."
                          },
                          "filename": {
                            "type": "string",
                            "example": "proofsnap-captures-2026-10-07T1430Z-part-1-of-3.zip",
                            "description": "Nome de arquivo com que o download é salvo (Content-Disposition): proofsnap-captures-<data e hora UTC de início do job>, com -part-N-of-M quando há várias partes."
                          },
                          "download_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true,
                            "description": "GET pré-assinado do ZIP da parte, válido até url_expires_at. null quando o job expirou."
                          }
                        }
                      }
                    },
                    "download_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Fim da janela de download: 7 dias após a meia-noite UTC anterior ao início da primeira parte (o ciclo de vida do armazenamento exclui as partes depois disso)."
                    },
                    "url_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Quando os valores de download_url desta resposta deixam de funcionar: no máximo 24 horas, nunca depois de download_expires_at, nunca depois das credenciais de assinatura. Consulte novamente depois disso para obter links novos."
                    },
                    "error": {
                      "type": "string",
                      "nullable": true,
                      "example": "export timed out"
                    },
                    "expired_reason": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "window_ended",
                        "superseded",
                        "rights_changed",
                        "capture_erased",
                        "company_deleted",
                        null
                      ],
                      "description": "Definido quando status é expired: window_ended (download_expires_at passou), superseded (uma exportação mais recente do mesmo escopo), rights_changed (os seus direitos não cobrem mais o job), capture_erased (uma captura que ele contém foi apagada), company_deleted (os dados do seu escritório foram excluídos)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "EXPORT_JOB_NOT_FOUND: o job não existe, é de outra conta, o seu registro expirou (8 dias após o término) ou a requisição foi feita com uma chave de teste.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookSubscriptions",
        "summary": "Listar assinaturas de webhook",
        "description": "As assinaturas de webhook da conta, das mais recentes às mais antigas, ativas e desativadas, sem segredos. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Assinaturas.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookSubscription",
        "summary": "Inscrever-se em eventos da conta",
        "description": "Inscreve um endpoint https em eventos da conta: cada evento correspondente da conta é enviado a ele por POST, assinado com o segredo próprio da assinatura (retornado uma única vez nesta resposta). Exige um endereço de e-mail confirmado (403 EMAIL_NOT_VERIFIED). Permitido enquanto uma assinatura estiver em atraso (nada é cobrado). Limites de assinaturas ATIVAS por conta: 25 em developer, startup, growth, custom, legal_solo e legal_seats, 10 em website_archive, 0 em archive_only e sandbox (409 WEBHOOK_SUBSCRIPTION_LIMIT). A URL segue as regras de webhook_url (https, sem credenciais, sem endereço privado ou reservado; verificada novamente a cada entrega). Idempotency-Key é suportado: uma nova tentativa com a mesma chave e o mesmo corpo em até 24 horas recebe de volta a primeira resposta, com o segredo, em vez de uma segunda assinatura. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-la.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "example": "https://hooks.example.com/proofsnap"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples, no máximo 200 caracteres."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado. `secret` é exibido apenas aqui.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreated"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (events, filters, description, campo desconhecido), MISSING_FIELD (url) ou INVALID_URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT, IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED (a URL aponta para um endereço bloqueado) ou IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions/{subscription_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SubscriptionId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookSubscription",
        "summary": "Obter uma assinatura de webhook",
        "description": "Uma assinatura com seu estado de entrega, sem o segredo. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A assinatura.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: desconhecida, excluída ou de outra conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhookSubscription",
        "summary": "Alterar ou reativar uma assinatura de webhook",
        "description": "Altera eventos, filtros (null ou {} os remove) ou descrição, ou reativa uma assinatura desativada com status \"active\" (zera os contadores de falha; verificado contra o limite de assinaturas ativas). A URL não pode ser alterada: crie uma nova assinatura. Exige um endereço de e-mail confirmado. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "type": "object",
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                      }
                    ],
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A assinatura alterada.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: desconhecida, excluída ou de outra conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT (reativação acima do limite).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookSubscription",
        "summary": "Excluir uma assinatura de webhook",
        "description": "Exclui a assinatura, o segredo dela e as entregas na fila. 404 para uma assinatura desconhecida ou já excluída (trate-a como removida). Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usar esta rota.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Excluído.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: desconhecida, excluída ou de outra conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listEvents",
        "summary": "Log de eventos",
        "description": "Todos os eventos emitidos para a conta nos últimos 30 dias, tenha uma assinatura os recebido ou não, do mais recente para o mais antigo. Uma chave de API vê os eventos do ambiente em que roda (uma chave live, os eventos live; uma chave de teste, os eventos de teste); uma sessão do painel vê os eventos live. Use para preencher lacunas (backfill) ou para fazer polling em vez de assinar. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Somente este tipo de evento.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "De 1 a 100, padrão 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Um id de evento: a página começa depois dele (envie next_cursor). Um id de outra conta ou de outro ambiente, ou com mais de 30 dias, retorna 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Uma página de eventos.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Passe como starting_after para obter a próxima página; null na última página."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (type, limit ou starting_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "whoami",
        "summary": "Quem sou eu",
        "description": "O que é a credencial: plano, ambiente, se a chave de API é somente leitura, e o id, o nome e o prefixo da chave (null para uma sessão do painel). As integrações usam isso para testar uma conexão e identificá-la. Nenhum endereço de e-mail e nenhum id de conta. Chave de API ou sessão do painel; uma chave somente leitura (Website Archive) pode usá-lo.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A credencial.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string",
                      "nullable": true,
                      "example": "startup",
                      "enum": [
                        "developer",
                        "startup",
                        "growth",
                        "custom",
                        "legal_solo",
                        "legal_seats",
                        "website_archive",
                        "archive_only",
                        "sandbox",
                        null
                      ]
                    },
                    "api_read_only": {
                      "type": "boolean",
                      "description": "true para uma chave de API de uma conta Website Archive (ela pode ler e assinar, não criar capturas)."
                    },
                    "env": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ],
                      "description": "O ambiente em que a credencial é executada."
                    },
                    "key": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "key_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "prefix": {
                          "type": "string",
                          "enum": [
                            "ps_live_",
                            "ps_test_"
                          ]
                        }
                      }
                    },
                    "email_verified": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/events": {
      "get": {
        "tags": [
          "Capturas"
        ],
        "operationId": "listCaptureEvents",
        "summary": "Cadeia de eventos da captura (exclusões e retenções legais)",
        "description": "O registro da conta, que torna adulterações detectáveis, do que aconteceu com as suas capturas: lixeira, restauração, exclusão permanente (erase), a limpeza da lixeira, exclusão por retenção, exclusão junto com a conta ou o escritório, retenção legal aplicada e liberada (uma liberação pelo sistema depois do fim da assinatura tem actor `system` e detail `subscription_ended`), o resultado do S3 Object Lock e a importação de um pacote de provas (type `import`, detail = o tipo de pacote). Do mais antigo para o mais recente. A cadeia sobrevive às capturas e aos dados da conta: contém apenas ids e hashes de manifesto, nenhum conteúdo de captura e nenhum texto livre.\n\nComo verificar: (1) para cada evento, calcule o SHA-256 sobre o JSON canônico dos campos com hash (verification.hashed_fields; chaves ordenadas, sem espaços em branco, valores ausentes como null) e compare com `hash`; (2) verifique se cada `prev_hash` é igual ao `hash` do evento anterior (64 zeros para seq 1) e se seq não tem lacunas; (3) para uma ancoragem, verifique se `head_hash` é igual ao `hash` do evento `to_seq`, grave `ots_proof_base64` em um arquivo (decodificado de base64) e execute `ots verify -d <head_hash> anchor.ots` (cliente OpenTimestamps): isso prova que a cadeia até aquele evento existia no mais tardar no momento do bloco do Bitcoin. As ancoragens são feitas uma vez por dia para cada conta com eventos de um dia anterior.\n\nChave de API ou sessão do painel, a própria conta; `scope=company` dá ao proprietário de um escritório a cadeia das capturas do escritório (`org` escolhe um quando o chamador é proprietário de vários; um escritório mantido como company archive apenas pela sessão do painel). Uma chave de teste recebe 403 FORBIDDEN: a cadeia abrange capturas live e não pode ser filtrada sem ser quebrada.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "De 1 a 500, padrão 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Um seq de evento: a página começa depois dele (passe next_starting_after).",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "own",
                "company"
              ],
              "default": "own"
            }
          },
          {
            "name": "org",
            "in": "query",
            "required": false,
            "description": "scope=company: o id do escritório.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Uma página da cadeia.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "chain": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "head": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "seq": {
                          "type": "integer"
                        },
                        "head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "anchored_seq": {
                          "type": "integer"
                        }
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEvent"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "integer",
                      "nullable": true
                    },
                    "anchors": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEventAnchor"
                      }
                    },
                    "verification": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "hashed_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "genesis_prev_hash": {
                          "type": "string"
                        },
                        "anchor": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: limit, starting_after ou scope inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: a requisição foi feita com uma chave de teste, ou com scope=company por alguém que não é proprietário do escritório.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/legal-holds": {
      "post": {
        "tags": [
          "Capturas"
        ],
        "operationId": "bulkLegalHold",
        "summary": "Aplicar ou liberar retenções legais em lote",
        "description": "Aplica (enabled true) ou libera (false) a retenção legal de várias capturas de uma vez. selector tem exatamente um destes: `capture_ids` (1 a 500 ids), `matter_id` (as capturas de um caso que o chamador pode abrir), `host` (as próprias capturas do chamador desse hostname) ou `schedule_id` (as próprias capturas do chamador feitas por um dos agendamentos do chamador). Cada captura passa pelas regras de PATCH /captures/{capture_id} com action legal_hold e recebe o seu próprio resultado: applied, unchanged (já está nesse estado) ou recusada com o código de erro (por exemplo FORBIDDEN, CAPTURE_IN_TRASH, CAPTURE_PROCESSING, LEGAL_HOLD_OWNER_ONLY, LEGAL_HOLD_MATTER, LEGAL_HOLD_SOURCE para um tarjamento cuja captura de origem ainda está retida, CAPTURE_NOT_FOUND, LEGAL_HOLD_NOT_AVAILABLE para uma captura em modo de teste). Os tarjamentos feitos a partir de cada captura seguem a sua retenção, também para uma captura informada como unchanged; quando isso não termina dentro da requisição, é concluído em poucos minutos. Aplicar retenções exige um plano pago: o sandbox gratuito recebe 403 LEGAL_HOLD_NOT_AVAILABLE (details.reason \"plan\") para a requisição inteira; liberar funciona em todos os planos. Uma retenção aplicada aqui tem legal_hold_source \"bulk\"; liberá-la em lote ou uma a uma funciona da mesma forma. No máximo 500 capturas por requisição: um caso, host ou agendamento com mais é cortado (`truncated: true`, envie de novo). O Object Lock legal hold do S3 das primeiras 25 capturas alteradas é aplicado na requisição, o restante em poucos minutos (`s3_legal_hold.status` pending). Custo: uma unidade do limite de requisições a cada 10 capturas (no mínimo uma); uma requisição que precise de mais do que uma janela permite retorna 400 RATE_LIMIT_COST_TOO_HIGH. Chave de API ou sessão do painel; no Website Archive a chave de API é somente leitura (403 PLAN_API_READ_ONLY). Uma sessão do painel ou chave cujo endereço de e-mail não foi confirmado recebe 403 EMAIL_NOT_VERIFIED. Não é bloqueado por uma assinatura em atraso.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled",
                  "selector"
                ],
                "additionalProperties": false,
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Texto simples mantido com cada retenção aplicada (não com uma liberação); nunca nas cadeias com hash."
                  },
                  "selector": {
                    "type": "object",
                    "minProperties": 1,
                    "maxProperties": 1,
                    "properties": {
                      "capture_ids": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 500,
                        "items": {
                          "type": "string"
                        }
                      },
                      "matter_id": {
                        "type": "string"
                      },
                      "host": {
                        "type": "string"
                      },
                      "schedule_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultados por captura.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "selector": {
                      "type": "string",
                      "enum": [
                        "capture_ids",
                        "matter_id",
                        "host",
                        "schedule_id"
                      ]
                    },
                    "requested": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "applied": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "refused": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "result": {
                            "type": "string",
                            "enum": [
                              "applied",
                              "unchanged",
                              "refused"
                            ]
                          },
                          "code": {
                            "type": "string",
                            "description": "O código de erro de uma recusa."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (corpo, seletor, mais de 500 capture_ids), RATE_LIMIT_COST_TOO_HIGH.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED, PLAN_API_READ_ONLY, LEGAL_HOLD_NOT_AVAILABLE (aplicação sem plano pago, details.reason plan).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND (matter_id que o chamador não pode abrir), SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports": {
      "post": {
        "tags": [
          "Importação de pacotes"
        ],
        "operationId": "createImport",
        "summary": "Iniciar uma importação de pacote",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Cria uma importação e inicia o seu upload. Envie o tamanho exato do ZIP em bytes; a resposta contém uma URL PUT pré-assinada por parte de 32 MiB (a última menor), válida por 1 hora (`upload.expires_at`; nunca por mais tempo que as credenciais de assinatura do servidor). Envie por PUT os bytes de cada parte para a sua URL, guarde o cabeçalho `ETag` de cada resposta e depois chame POST /imports/{import_id}/complete em até 24 horas. Com `matter_id`, a captura aceita é adicionada a esse caso: você precisa poder adicionar capturas a ele agora (um caso aberto que você pode abrir; a importação de um assento de escritório somente a um caso desse escritório). Cada criação conta para o limite diário de 50.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "size_bytes"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "O nome de arquivo a exibir (texto simples: sem caracteres de controle, invisíveis ou de direção de texto)."
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2147483648,
                    "description": "Tamanho exato do ZIP em bytes, no máximo 2 GiB."
                  },
                  "matter_id": {
                    "type": "string",
                    "description": "Opcional: adiciona a captura aceita a este caso."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Importação criada; envie as partes.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "uploading"
                      ]
                    },
                    "upload": {
                      "type": "object",
                      "properties": {
                        "upload_id": {
                          "type": "string"
                        },
                        "part_size": {
                          "type": "integer",
                          "example": 33554432
                        },
                        "parts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "part_number": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "URL pré-assinada de UploadPart do S3: faça PUT de exatamente size_bytes bytes (a URL assina o Content-Length) e leia o cabeçalho de resposta ETag. Nenhum outro cabeçalho é necessário."
                              },
                              "size_bytes": {
                                "type": "integer",
                                "description": "O tamanho exato em bytes desta parte: part_size para todas as partes exceto a última, o restante para a última (a parte n começa em (n - 1) x part_size)."
                              }
                            }
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (body, filename, size_bytes, matter_id, campo desconhecido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: a assinatura está em atraso ou não paga.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED (details.reason plan: somente planos Legal; live_credentials_required: uma chave de teste, uma sessão sem cobrança vinculada ou o sandbox), EMAIL_NOT_VERIFIED, FORBIDDEN (importação feita por um assento de escritório para um caso de outro escritório).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_LIMIT (3 importações em andamento; details.limit, details.in_progress), MATTER_SEALED, MATTER_ARCHIVED, COMPANY_READ_ONLY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "IMPORT_TOO_LARGE: size_bytes acima de 2 GiB (details.max_bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: o limite por minuto, ou o limite diário de 50 importações (details.action import_create, details.daily_limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Importação de pacotes"
        ],
        "operationId": "listImports",
        "summary": "Listar suas importações",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Suas importações, das mais recentes para as mais antigas. Importações finalizadas são mantidas por 30 dias (a captura de uma importação aceita permanece). Um upload que passou das suas 24 horas aparece como expirado.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "next_cursor da página anterior."
          }
        ],
        "responses": {
          "200": {
            "description": "Uma página de importações.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Import"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (limit, cursor).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "ID da importação (imp_ seguido de 16 a 24 letras e dígitos). Um ID malformado retorna 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "get": {
        "tags": [
          "Importação de pacotes"
        ],
        "operationId": "getImport",
        "summary": "Obter uma importação",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "A importação e, quando final, seu resultado. Consulte-a após POST /imports/{import_id}/complete; a verificação costuma levar segundos, e um pacote de 2 GiB até alguns minutos. Com ?upload=true em um upload ainda em andamento, a resposta adiciona upload: novas URLs pré-assinadas (parts) para as partes que o armazenamento ainda não tem e as partes já recebidas (received: part_number, etag, size_bytes; null quando não puderam ser listadas, e então todas as partes são pré-assinadas novamente), ou completed: true quando o upload já foi concluído (envie complete novamente). Use isso para retomar após uma queda de rede, uma URL de parte expirada (elas duram no máximo 1 hora, a janela de upload 24 horas) ou um recarregamento da página. ?upload=true custa uma unidade do limite de requisições por minuto.",
        "responses": {
          "200": {
            "description": "A importação.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Import"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND (também para a importação de outra conta).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED (apenas com ?upload=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "name": "upload",
            "in": "query",
            "required": false,
            "description": "true (ou 1): adiciona URLs de partes novas e as partes recebidas de um upload em andamento.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "Importação de pacotes"
        ],
        "operationId": "cancelImport",
        "summary": "Cancelar um upload",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Cancela uma importação cujo upload ainda está em andamento: o upload é abortado, as partes são excluídas e a importação é removida (depois disso, GET retorna 404).",
        "responses": {
          "200": {
            "description": "Cancelado.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: a importação não está mais sendo enviada (details.status).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}/complete": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "ID da importação (imp_ seguido de 16 a 24 letras e dígitos). Um ID malformado retorna 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "post": {
        "tags": [
          "Importação de pacotes"
        ],
        "operationId": "completeImport",
        "summary": "Concluir o upload e verificar",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Conclui o upload e inicia a verificação. parts é opcional: envie cada número de parte (1 a N, em ordem) com o ETag que o PUT da parte retornou, ou omita-o e o servidor lê as partes enviadas a partir do armazenamento (útil em um navegador que não consegue ler o cabeçalho ETag). Verifica se o tamanho armazenado é igual a size_bytes. Consulte GET /imports/{import_id}. Uma lista de partes que o armazenamento recusa, ou uma parte ainda não enviada (details.missing_parts), resulta em 400 e o upload continua aberto: envie o que falta (GET /imports/{import_id}?upload=true fornece URLs novas) e envie novamente. Enviar complete de novo depois que uma resposta se perdeu é seguro: um complete que ainda está em execução retorna 409, um que morreu é assumido após 2 minutos, e um upload que já foi concluído segue para a verificação. Um tamanho diferente de size_bytes descarta o upload (422 IMPORT_SIZE_MISMATCH; a importação é rejeitada, crie uma nova).",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "part_number",
                        "etag"
                      ],
                      "properties": {
                        "part_number": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "etag": {
                          "type": "string",
                          "description": "O cabeçalho de resposta ETag do PUT da parte, com ou sem aspas."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Verificação iniciada.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "verifying"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (partes fora de ordem ou que não correspondem às partes enviadas; ou, sem parts, uma parte ainda não enviada: details.missing_parts).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED, EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: a importação não está mais sendo enviada (details.status, por exemplo verifying ou expired).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "IMPORT_SIZE_MISMATCH (details.expected, details.actual).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Envie a sua chave de API como `Authorization: Bearer ps_live_...` (o nome do esquema não diferencia maiúsculas de minúsculas). Um cabeçalho ausente ou malformado retorna 401 MISSING_API_KEY. Uma chave desconhecida, revogada ou expirada, ou uma conta sem acesso à API, retorna 401 INVALID_API_KEY com `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. O 403 é reservado para uma credencial válida que não tem permissão para fazer algo (FORBIDDEN, restrições de plano). Uma chave `ps_live_` de uma conta sem conta de cobrança vinculada (cliente Stripe) funciona como chave de teste: as capturas recebem marca d'água e nunca são cobradas, e include_eidas é recusado com 403 EIDAS_REQUIRES_LIVE_KEY. O gateway armazena em cache a decisão sobre uma chave por até 60 segundos, então uma chave revogada pode continuar funcionando por até 60 segundos. Se a credencial não puder ser verificada por causa de uma falha temporária do backend, o gateway retorna 503 SERVICE_UNAVAILABLE; essa resposta não é armazenada em cache, então tente novamente com backoff. O desafio `WWW-Authenticate` é retornado sem alterações em um 401 do gateway (a verificação da credencial); em um 401 de um handler de rota, o gateway entrega o mesmo desafio renomeado para `x-amzn-Remapped-WWW-Authenticate`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "ID da requisição, útil também em solicitações de suporte. Enviado nas respostas de sucesso; os corpos de erro o trazem como request_id.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requisições permitidas na janela atual de 60 segundos. Cabeçalho legado, mantido ao lado de RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requisições restantes na janela atual. Cabeçalho legado, mantido ao lado de RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Horário ISO 8601 em que a janela atual termina. Cabeçalho legado, mantido ao lado de RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RetryAfter": {
        "description": "Segundos de espera antes de tentar novamente.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "Capturas incluídas no plano por mês (chaves live em planos com cota mensal).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Capturas incluídas restantes neste mês.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaUsed": {
        "description": "Capturas concluídas neste mês.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaCap": {
        "description": "O máximo mensal que se aplica a quem chama (padrão do plano, teto próprio ou limite do escritório).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgLimit": {
        "description": "Somente membros do escritório: o limite mensal de todo o escritório, quando o proprietário definiu um.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgUsed": {
        "description": "Somente membros do escritório: capturas admitidas para todo o escritório neste mês.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxLimit": {
        "description": "Sandbox (sessão do painel sem plano): franquia vitalícia de capturas de teste com marca d'água (50).",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxRemaining": {
        "description": "Sandbox: franquia restante.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxUsed": {
        "description": "Sandbox: franquia usada.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "\"true\" quando esta resposta repete uma requisição anterior com o mesmo Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "WWWAuthenticate": {
        "description": "Desafio RFC 6750 enviado com todo 401. Em um 401 retornado por um handler de rota e não pelo gateway, o API Gateway o entrega renomeado para `x-amzn-Remapped-WWW-Authenticate`. Uma chave de API inválida, revogada ou expirada, ou um token de sessão inválido ou revogado (INVALID_API_KEY): `Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Um cabeçalho Authorization ausente ou malformado (MISSING_API_KEY): `Bearer realm=\"ProofSnap API\"`.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"ProofSnap API\", error=\"invalid_token\""
      },
      "RateLimitPolicy": {
        "description": "Política do limite de requisições, seguindo o rascunho da IETF draft-ietf-httpapi-ratelimit-headers (forma de structured field; baseada em rascunho, pode mudar com o rascunho): `\"default\";q=<requests per window>;w=60`.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=120;w=60"
      },
      "RateLimit": {
        "description": "Estado atual do limite de requisições, conforme o rascunho do IETF draft-ietf-httpapi-ratelimit-headers (baseado no rascunho): `\"default\";r=<requests left>;t=<seconds until the window resets>`. Enviado nas respostas das rotas com limite de requisições (captures individual e em lote, compare, redact, certify-hash) depois que o limitador é executado, não apenas em 429 (uma requisição recusada antes do limitador, por exemplo por autenticação, não o recebe).",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=117;t=42"
      }
    },
    "parameters": {
      "CaptureId": {
        "name": "capture_id",
        "in": "path",
        "required": true,
        "description": "ID de captura, certificado, comparação, tarjamento ou pacote importado. Os IDs começam com cap_, cert_, cmp_, red_ ou imp_. Um ID malformado retorna 404 CAPTURE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "cap_a1B2c3D4e5F6g7H8"
        }
      },
      "ScheduleId": {
        "name": "schedule_id",
        "in": "path",
        "required": true,
        "description": "ID do agendamento. Um agendamento de outra conta retorna 404 SCHEDULE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "sch_a1B2c3D4e5F6g7H8"
        }
      },
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "description": "ID do caso. Um caso do qual você não é membro retorna 404 MATTER_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "mat_a1B2c3D4e5F6g7H8"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Chave opcional do cliente que torna uma nova tentativa segura. Apenas os primeiros 255 caracteres são usados, e as chaves valem para a sua conta por 24 horas. Uma nova tentativa com a mesma chave e o mesmo corpo retorna o corpo da resposta original com `Idempotent-Replay: true` (202 para uma captura avulsa, um lote ou uma comparação, 201 para um tarjamento; `status` é atualizado para o status atual da captura, comparação ou tarjamento, e de cada captura de um lote); nenhuma captura nova é criada ou cobrada. A mesma chave com um corpo diferente retorna 422 IDEMPOTENCY_KEY_REUSED. Uma nova tentativa enquanto a primeira requisição ainda está sendo aceita retorna 409 IDEMPOTENCY_IN_PROGRESS com o ID original em `error.details.capture_id` (o batch_id no caso de um lote).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "SubscriptionId": {
        "name": "subscription_id",
        "in": "path",
        "required": true,
        "description": "ID da assinatura de webhook. Uma assinatura de outra conta retorna 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "whs_a1B2c3D4e5F6g7H8"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "RATE_LIMITED: requisições demais na janela atual de 60 segundos. `error.details.retry_after_seconds` repete o Retry-After. RateLimit informa r=0 e os segundos até a janela ser reiniciada.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Too many requests. See Retry-After header.",
                "details": {
                  "retry_after_seconds": 42
                }
              },
              "request_id": "..."
            }
          }
        }
      },
      "InternalError": {
        "description": "INTERNAL_ERROR: erro inesperado no servidor. Tente novamente com backoff exponencial.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AuthUnavailable": {
        "description": "SERVICE_UNAVAILABLE: o gateway não conseguiu verificar a credencial (falha temporária do backend). Não fica em cache: tente novamente com backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "SERVICE_UNAVAILABLE",
                "message": "Authentication is temporarily unavailable. Please retry."
              },
              "request_id": "..."
            }
          }
        }
      },
      "EidasOrAuthUnavailable": {
        "description": "EIDAS_UNAVAILABLE: include_eidas foi solicitado, mas o carimbo do tempo qualificado não está configurado nesta implantação; tente novamente sem include_eidas. SERVICE_UNAVAILABLE: o gateway não conseguiu verificar a credencial (falha temporária do backend, não armazenada em cache); tente novamente com backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401, dois códigos. MISSING_API_KEY: o cabeçalho Authorization está ausente ou malformado (`WWW-Authenticate: Bearer realm=\"ProofSnap API\"`). INVALID_API_KEY: a credencial foi enviada, mas não é válida: uma chave de API desconhecida, revogada ou expirada, uma conta sem acesso à API ou um token de sessão inválido ou revogado (`WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`). Não tente novamente com a mesma credencial. 403 significa que a credencial é válida, mas não tem permissão para fazer isso (FORBIDDEN, restrições do plano).",
        "headers": {
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "value": {
                  "error": {
                    "code": "MISSING_API_KEY",
                    "message": "Missing or malformed Authorization header. Send Authorization: Bearer <your API key>."
                  },
                  "request_id": "..."
                }
              },
              "invalid": {
                "value": {
                  "error": {
                    "code": "INVALID_API_KEY",
                    "message": "The API key is invalid, revoked or expired, or the account has no API access."
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Todo erro tem este formato. As respostas de erro trazem o cabeçalho `X-Request-ID` com o mesmo valor de `request_id`.\n\nTamanho da requisição: o corpo de uma requisição pode ter no máximo 6 MB (o limite de payload do Lambda; o próprio API Gateway para em 10 MB). Um corpo maior é recusado antes de chegar ao código da API, então essa resposta não tem este formato.\n\n| HTTP | code | Significado |\n| --- | --- | --- |\n| 400 | `MALFORMED_REQUEST` | O corpo não é um JSON válido (mensagem \"Request body is not valid JSON\"), ou a requisição é inválida: quando `details.reason` indica qual campo e por quê, `message` repete esse motivo; caso contrário, é \"The request is invalid.\" |\n| 400 | `MISSING_FIELD` | Falta um campo obrigatório. |\n| 400 | `INVALID_URL` | A URL não é uma URL http(s) válida, tem mais de 2048 caracteres, ou um webhook_url não é https. |\n| 400 | `INVALID_HASH` | certify-hash: um hash não tem 64 caracteres hexadecimais. |\n| 400 | `INVALID_TIMESTAMP_TYPE` | options.timestamp_type não é \"bitcoin\". |\n| 400 | `INVALID_VIEWPORT` | options.viewport não é \"desktop\" nem \"mobile\". |\n| 400 | `METADATA_TOO_LARGE` | options.metadata tem mais de 10 chaves, um valor que não é string ou um valor com mais de 500 caracteres. |\n| 400 | `INVALID_EVIDENCE_PACK_OPTIONS` | options.evidence_pack é inválido. |\n| 400 | `INVALID_BATCH` | O urls do lote não é um array de 2 a 50 URLs válidas. |\n| 400 | `REDACTION_NO_OPS` | O tarjamento não tem operações, ou nenhuma delas encontrou correspondência. |\n| 400 | `INVALID_CRON` | Não foi possível interpretar cron_expr. |\n| 400 | `INTERVAL_TOO_SHORT` | A cadência do agendamento está abaixo do mínimo do plano. |\n| 400 | `RATE_LIMIT_COST_TOO_HIGH` | A requisição precisa de mais unidades do limite de requisições do que uma janela permite; `details.max_per_window`, `details.window_seconds`, `details.requested`. Divida-a. |\n| 400 | `WEBHOOK_REQUIRES_API_KEY` | Um `webhook_url` foi enviado a partir de uma sessão do painel. Os webhooks são assinados com o segredo de webhook de uma chave de API, então precisam de uma chave de API. |\n| 401 | `MISSING_API_KEY` | Cabeçalho Authorization ausente ou malformado. `WWW-Authenticate: Bearer realm=\"ProofSnap API\"`. |\n| 401 | `INVALID_API_KEY` | Chave desconhecida, revogada ou expirada, sem acesso à API, ou token de sessão inválido ou revogado. `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. |\n| 402 | `QUOTA_EXCEEDED` | Máximo mensal ou franquia do sandbox atingido (também no `error` de uma certificação em segundo plano recusada por esse motivo). |\n| 402 | `PAYMENT_REQUIRED` | A assinatura live está em atraso ou não paga (também no `error` de uma captura agendada pulada por esse motivo). |\n| 403 | `FORBIDDEN` | O recurso pertence a outra pessoa, ou a ação é exclusiva do proprietário. Mensagem: \"You do not have access to this resource.\" (details.reason dá mais detalhes quando ajuda). |\n| 403 | `EMAIL_NOT_VERIFIED` | Uma sessão do painel cujo endereço de e-mail não foi confirmado tentou criar algo persistente, pago ou que convida pessoas (capturas, lotes, comparações, tarjamentos, chaves de API, segredos de webhook, checkout, mudanças de plano, mudanças de assentos, certify-hash, agendamentos, convites do escritório, casos e toda alteração neles, membros de casos, capturas atribuídas a casos, compartilhamentos e sua revogação, selos, exportações, retenções legais). As leituras continuam liberadas. Mensagem: \"Confirm your email address first. Use the link we sent you, or send a new one from the dashboard.\" Uma chave de API é recusada da mesma forma quando o endereço de e-mail da sua conta não foi confirmado. |\n| 403 | `EIDAS_REQUIRES_LIVE_KEY` | include_eidas enviado com uma chave de teste (uma chave ps_live_ de uma conta sem conta de cobrança vinculada conta como tal), ou para uma comparação ou tarjamento de uma captura de teste (`details.reason`). Uma chave live ou sessão que roda como teste por não haver conta de cobrança vinculada recebe `details.reason` \"no_billing_account\". |\n| 403 | `MATTERS_NOT_ENABLED` | Casos exigem um plano Legal. |\n| 403 | `SCHEDULE_LIMIT_REACHED` | Limite de agendamentos ativos do plano atingido (ou o plano não tem agendamentos). |\n| 403 | `ON_CHANGE_REQUIRES_PLAN` | O plano não permite monitoramento on_change. |\n| 403 | `PLAN_ARCHIVE_ONLY` | O plano Archive only mantém suas provas legíveis, disponíveis para download e verificáveis; ele não cria capturas, agendamentos, certificações, comparações nem tarjamentos. |\n| 403 | `PLAN_NOT_AVAILABLE` | O plano não pode ser escolhido por esta conta: o Archive only é contratado depois que uma assinatura paga da API terminou (enquanto as provas ainda estão guardadas) ou alcançado mudando de plano a partir de developer, startup, growth, website_archive ou legal_solo; o company archive apenas pelo proprietário de um escritório cuja assinatura terminou, dentro da sua janela de download. `details.reason` diz o motivo. |\n| 403 | `PLAN_API_READ_ONLY` | Plano Website Archive: a chave de API é somente leitura (listar, baixar, verificar, webhooks). Crie capturas, agendamentos e certificações no painel, ou use um plano de API. |\n| 403 | `LEGAL_HOLD_OWNER_ONLY` | Apenas o proprietário do escritório libera a retenção legal de uma captura do escritório (um assento pode aplicá-la). |\n| 403 | `LEGAL_HOLD_NOT_AVAILABLE` | Aplicar uma retenção legal exige um plano pago (`details.reason` \"plan\": o sandbox gratuito, ou uma conta cuja assinatura terminou) e uma captura live (`details.reason` \"test_capture\": uma captura em modo de teste com marca d'água). A liberação de uma retenção nunca é recusada por isso. |\n| 403 | `IMPORT_NOT_ALLOWED` | A importação de pacote exige um plano Legal (`details.reason` \"plan\") e uma chave de API live ou uma sessão do painel com cobrança vinculada (`details.reason` \"live_credentials_required\"). |\n| 404 | `CAPTURE_NOT_FOUND` | A captura não existe, foi excluída, ou o ID está malformado. |\n| 404 | `MATTER_NOT_FOUND` | O caso não existe ou você não é membro dele. |\n| 404 | `IMPORT_NOT_FOUND` | A importação não existe, pertence a outra conta, ou o ID está malformado. |\n| 404 | `WEBHOOK_SUBSCRIPTION_NOT_FOUND` | A assinatura de webhook não existe, foi excluída, ou pertence a outra conta. |\n| 404 | `SCHEDULE_NOT_FOUND` | O agendamento não existe ou pertence a outra conta. |\n| 404 | `NOT_FOUND` | Outro recurso não encontrado (formato de hash do ledger, token de compartilhamento, job de exportação, membro). |\n| 404 | `EXPORT_JOB_NOT_FOUND` | Um job de exportação de capturas não existe, pertence a outra conta, expirou, ou foi solicitado com uma chave de teste. |\n| 405 | `METHOD_NOT_ALLOWED` | O método HTTP não é permitido nesta rota. |\n| 409 | `CAPTURE_PROCESSING` | A captura ainda está em processamento e ainda não pode ser excluída (nem ter sua retenção legal alterada). |\n| 409 | `CAPTURE_LEGAL_HOLD` | A captura está sob retenção legal e não pode ser excluída. |\n| 409 | `CAPTURE_IN_TRASH` | Retenção legal: a captura está na lixeira; restaure-a primeiro. |\n| 409 | `CAPTURE_HOLD_GRACE` | Exclusão permanente dentro do período de carência após a liberação da retenção legal da captura; `details.until`. Mover para a lixeira é permitido. |\n| 409 | `LEGAL_HOLD_MATTER` | A retenção da captura foi aplicada pela retenção legal do seu caso, que continua ativa: libere-a no caso (`details.matter_id` para quem pode ler o caso). |\n| 409 | `LEGAL_HOLD_SOURCE` | A retenção de um tarjamento ou comparação acompanha uma captura a partir da qual foi feito (no caso de uma comparação, qualquer uma das capturas comparadas), que continua sob retenção legal (aplicada nessa captura ou por meio do seu caso): o pacote permanece retido até que essa retenção seja liberada (`details.source_capture_id`). |\n| 409 | `COMPANY_READ_ONLY` | A assinatura do escritório terminou (ou ele é mantido como company archive): seus casos podem ser abertos, exportados e ter os links de compartilhamento revogados, nada mais, e nada novo é derivado das suas capturas. |\n| 409 | `WEBHOOK_SUBSCRIPTION_LIMIT` | Limite de assinaturas de webhook ativas do plano atingido (25 nos planos de API e Legal, 10 no Website Archive, 0 no Archive only e no sandbox); `details.limit`, `details.active`. |\n| 409 | `WEBHOOK_SECRET_ROTATION_PENDING` | O segredo de webhook anterior ainda está dentro da sua sobreposição de 24 horas; `details.previous_secret_expires_at`. |\n| 409 | `IDEMPOTENCY_IN_PROGRESS` | A primeira requisição com este Idempotency-Key ainda está sendo aceita. |\n| 409 | `COMPARE_NOT_READY` | As duas capturas precisam estar concluídas antes da comparação. |\n| 409 | `REDACTION_NOT_READY` | A captura de origem precisa estar concluída antes do tarjamento. |\n| 409 | `MATTER_SEALED` | O caso (ou o caso da captura) está selado e não pode ser alterado. |\n| 409 | `MATTER_SEALING` | Um selo do caso está em andamento (no máximo 60 s): enquanto isso, nada de atribuição de captura, renomeação, mudança de membros, envio de captura vinculada para a lixeira ou segundo selo. `Retry-After`. |\n| 409 | `MATTER_ARCHIVED` | O caso está arquivado: sem novas capturas, renomeações, novos membros, compartilhamento ou exportação; reabra-o primeiro (PATCH status \"open\"). Remover uma captura ou um membro continua funcionando. |\n| 409 | `MATTER_CHANGED` | O log de custódia mudou durante a selagem; nada foi selado, tente novamente. |\n| 409 | `MATTER_LINKED` | Exclusão de captura: a captura é um anexo de um caso do qual quem fez a chamada não faz mais parte. O proprietário do caso a remove do caso primeiro. |\n| 409 | `MATTER_HAS_TRASHED_CAPTURES` | Selo do caso: uma captura do caso está na lixeira. Restaure-a ou remova-a do caso e então sele novamente. |\n| 409 | `MEMBER_HAS_INDIVIDUAL_SUBSCRIPTION` | Convite do escritório: a assinatura própria da API da pessoa convidada ainda se renova. Ela a cancela e então o proprietário a convida novamente. |\n| 409 | `IMPORT_LIMIT` | Já há 3 importações em andamento (enviando ou verificando); `details.limit`, `details.in_progress`. |\n| 409 | `IMPORT_STATE` | A importação não está em um estado que permita isso (concluir ou cancelar uma importação que não está mais sendo enviada); `details.status`. |\n| 409 | `EXPORT_JOB_RUNNING` | Uma exportação das suas capturas ainda está sendo preparada; `details.job_id` e `details.poll_path` a identificam. Inicie uma nova quando ela terminar. |\n| 413 | `IMPORT_TOO_LARGE` | O pacote tem mais de 2 GiB; `details.max_bytes`. |\n| 413 | `VERIFY_PACKAGE_TOO_LARGE` | POST /captures/{capture_id}/verify: o pacote armazenado tem mais de 256 MiB; verifique-o offline com os scripts incluídos. `details.size_bytes`, `details.limit_bytes`. |\n| 422 | `IDEMPOTENCY_KEY_REUSED` | Idempotency-Key reutilizado com um corpo diferente ou um ambiente de chave diferente (live ou test). |\n| 422 | `SSRF_BLOCKED` | A URL aponta para um endereço privado, de loopback, link-local ou bloqueado por outro motivo. |\n| 422 | `TARGET_UNREACHABLE` | O hostname da URL não tem registros DNS, ou não pôde ser resolvido. |\n| 422 | `REDACTION_SCREENSHOT_UNREADABLE` | Não foi possível decodificar a captura de tela para a tarja de área. |\n| 422 | `REDACTION_LEAK_DETECTED` | Um termo tarjado sobreviveu ao tarjamento fora da URL da captura; nada foi emitido. `details.files`, `details.rule_indexes`, `details.metadata_fields`, `details.in_capture_url`. |\n| 422 | `REDACTION_SOURCE_UNSUPPORTED` | Apenas capturas de páginas web podem ser tarjadas. `details.source` ou `details.manifest_type`. |\n| 422 | `REDACTION_SOURCE_INTEGRITY_FAILED` | Um arquivo da captura de origem não corresponde ao SHA-256 listado no seu manifesto assinado, ou está ausente; nada foi emitido. `details.mismatched`, `details.missing`. |\n| 422 | `COMPARE_PROFILE_MISMATCH` | POST /compare: as duas capturas foram feitas com um perfil de dispositivo diferente ou com configurações de captura diferentes; `details.differing`, `details.before`, `details.after`. Envie allow_profile_mismatch: true para comparar mesmo assim (o resultado de texto e imagem fica então indeterminado). |\n| 422 | `COMPARE_URL_MISMATCH` | POST /compare: as duas capturas são de páginas diferentes; `details.before`, `details.after` (url e final_url como origem e caminho). Envie allow_url_mismatch: true para comparar mesmo assim. |\n| 422 | `RULE902_NOT_AVAILABLE_FOR_MISMATCH` | POST /compare: include_rule_902 para capturas renderizadas de forma diferente (allow_profile_mismatch); `details.differing`. |\n| 422 | `COMPARE_SOURCE_INTEGRITY_FAILED` | Um arquivo de uma captura de origem do POST /compare não corresponde ao SHA-256 listado no seu manifesto assinado, ou está ausente; nada foi comparado nem cobrado. `details.capture_id`, `details.mismatched`, `details.missing`, `details.sources[]`. |\n| 422 | `REDACTION_PATTERN_TOO_SLOW` | Um padrão regex de text_redactions demorou demais nesta página (backtracking catastrófico); nada foi emitido nem cobrado. `details.rule_index`. Simplifique o padrão. |\n| 422 | `IMPORT_SIZE_MISMATCH` | O pacote enviado não tem o size_bytes anunciado; o envio foi descartado. `details.expected`, `details.actual`. |\n| 422 | `IMPORT_NOT_SUPPORTED` | Comparação ou tarjamento de um pacote de provas importado. |\n| 429 | `RATE_LIMITED` | Requisições demais na janela atual, ou foi atingido um limite diário de criação de casos (200), selos (20), exportações (50), convites de membros (50) ou importações de pacotes (50, `import_create`) (`details.daily_limit`, `details.action`). `Retry-After` diz quando tentar novamente. |\n| 429 | `CONCURRENT_CAPTURE_LIMIT` | Capturas demais da conta rodando ao mesmo tempo (developer 10, startup 25, growth 100, legal_solo e legal_seats 25, custom 200, website_archive 25, sandbox 5). Um lote inicia tantas URLs quanto couberem e lista o restante em `not_admitted`; sem nenhuma vaga livre, a requisição inteira é recusada. `details.limit`, `details.in_flight`, `details.requested`, `Retry-After`. |\n| 429 | `TEST_MODE_DAILY_LIMIT` | O trabalho em modo de teste (uma chave de teste ou uma sessão sem cobrança vinculada; com marca d'água, não cobrado) é limitado a 200 unidades por conta e por dia UTC (um lote conta suas URLs). `details.used`, `details.limit`, `details.reset_at`, `Retry-After`. |\n| 429 | `IMPORT_DAILY_BYTES_LIMIT` | Importação de pacote: o volume de importação da conta no dia UTC (5 GiB de tamanhos anunciados) foi atingido. `details.period`, `details.limit_bytes`, `details.used_bytes`, `details.requested_bytes`, `details.reset_at`. |\n| 429 | `IMPORT_MONTHLY_BYTES_LIMIT` | Importação de pacote: o volume de importação da conta no mês UTC (20 GiB) foi atingido. Mesmos detalhes. |\n| 500 | `INTERNAL_ERROR` | Erro inesperado. Tente novamente com backoff. |\n| 500 | `CAPTURE_FAILED` | Não foi possível iniciar a captura ou (no `error` de uma captura com falha) concluí-la. |\n| 500 | `CAPTURE_TIME_BUDGET_EXCEEDED` | No `error` de uma captura com falha, ou em um tarjamento síncrono: a execução esgotou seu orçamento de tempo e nenhum pacote foi emitido nem cobrado. |\n| 500 | `CAPTURE_SCREENSHOT_TIMEOUT` | No `error` de uma captura com falha: o navegador não terminou de renderizar a captura de tela: todas as formas de obtê-la (de uma vez, em faixas menores, a captura de tela do próprio navegador) excederam o tempo limite, assim como a única nova tentativa em um navegador recém-iniciado quando o orçamento de tempo deixou margem para ela. Nada foi cobrado; tente novamente. |\n| 500 | `CERTIFY_FAILED` | No `error` de uma certificação com falha: não foi possível concluir a certificação. |\n| 500 | `REDACTION_FAILED` | No `error` de um tarjamento com falha: não foi possível concluir o tarjamento. Um tarjamento recusado por um motivo conhecido traz o código desse motivo (por exemplo REDACTION_LEAK_DETECTED). |\n| 500 | `TIMESTAMP_SERVICE_UNAVAILABLE` | Selo do caso: a ancoragem OpenTimestamps falhou; nada mudou, tente novamente. |\n| 500 | `CONFIGURATION_ERROR` | Uma etapa do lado do servidor falhou ou não está configurada (`details.reason`). |\n| 503 | `SERVICE_UNAVAILABLE` | A autenticação está temporariamente indisponível (não foi possível verificar a credencial). Não fica em cache; tente novamente. |\n| 503 | `EIDAS_UNAVAILABLE` | include_eidas solicitado, mas o carimbo do tempo eIDAS não está configurado. |\n| 503 | `EIDAS_SEAL_RETRY` | Selo do caso: o carimbo do tempo eIDAS não chegou a tempo; nada foi selado. Tente novamente após `Retry-After`; a nova tentativa reutiliza o carimbo do tempo já emitido. |\n| 503 | `LIMIT_CHECK_UNAVAILABLE` | Selo do caso: não foi possível verificar seu limite diário; nada foi feito. Tente novamente. |\n| 504 | `UPSTREAM_TIMEOUT` | Uma etapa de processamento síncrona (certify-hash, exportação do caso) não respondeu a tempo. Ela ainda pode ser concluída: consulte o que `details` indica (`certify_id`, `poll_path`), ou tente novamente. |\n| 404 | `ORG_INVITE_NOT_FOUND` | Um convite do escritório desconhecido, endereçado a outra pessoa, que não está mais pendente, ou cujo escritório não está mais ativo (details.reason `company_inactive`). |\n| 409 | `ORG_INVITE_NO_SEATS` | Aceite de convite do escritório: todos os assentos do escritório estão em uso; o convite continua aberto. |\n| 409 | `ORG_INVITE_IN_OTHER_ORG` | Aceite de convite do escritório: quem fez a chamada ocupa um assento de outro escritório; o convite continua aberto. |\n| 409 | `ORG_INVITE_HAS_SUBSCRIPTION` | Aceite de convite do escritório: a assinatura própria da API de quem fez a chamada ainda se renova; o convite continua aberto. |\n| 409 | `ORG_OWNER_CANNOT_LEAVE` | O proprietário do escritório tentou sair do próprio escritório. |\n| 410 | `ORG_INVITE_EXPIRED` | Aceite de um convite do escritório com mais de 7 dias. |",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Código de erro legível por máquina (UPPER_SNAKE_CASE)."
              },
              "message": {
                "type": "string",
                "description": "Mensagem legível por humanos."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Contexto adicional opcional, por exemplo `reason`, `field` ou `retry_after_seconds`."
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "ID da requisição."
          }
        },
        "example": {
          "error": {
            "code": "INVALID_URL",
            "message": "URL is not a valid http(s) URL",
            "details": {
              "reason": "Only http and https schemes are allowed"
            }
          },
          "request_id": "..."
        }
      },
      "CaptureOptions": {
        "type": "object",
        "description": "Opções de captura. Todos os campos são opcionais.",
        "properties": {
          "scroll_full_page": {
            "type": "boolean",
            "description": "Captura a página rolável inteira. false captura apenas a área visível (viewport).",
            "default": true
          },
          "expand_collapsed": {
            "type": "boolean",
            "description": "Abre seções recolhidas antes da captura de tela: elementos <details> fechados, botões de acordeão e de divulgação (aria-expanded com aria-controls) e controles de texto do tipo \"ver mais\" / \"mostrar respostas\". Com salvaguardas: nada dentro de navegação, cabeçalhos, menus ou diálogos, nenhum link para outra URL, nenhum envio de formulário, nenhum rótulo que altere estado (curtir, seguir, comprar, excluir, ...), cada controle no máximo uma vez, e um controle que tenta sair da página interrompe a expansão e a URL original é carregada novamente. O resultado é registrado por controle em forensic_log.json (page_modifications.expand_collapsed: encontrados, abertos, deixados fechados, motivos de pulo, requisições iniciadas, altura da página antes e depois) com um resumo em metadata.json (expand_collapsed: requested, opened_total, left_closed_total); evidence.pdf e os certificados declaram apenas o que foi efetivamente aberto.",
            "default": true
          },
          "viewport": {
            "type": "string",
            "description": "desktop é 1920x1080 com pixel ratio 1; mobile é 390x844 com pixel ratio 3 e user agent do Safari mobile. Outros valores retornam 400 INVALID_VIEWPORT.",
            "enum": [
              "desktop",
              "mobile"
            ],
            "default": "desktop"
          },
          "viewport_width": {
            "type": "number",
            "minimum": 320,
            "maximum": 3840,
            "description": "Largura personalizada da viewport em pixels CSS. Deve ser enviada junto com viewport_height, caso contrário 400 MALFORMED_REQUEST."
          },
          "viewport_height": {
            "type": "number",
            "minimum": 240,
            "maximum": 4320,
            "description": "Altura personalizada da viewport em pixels CSS. Deve ser enviada junto com viewport_width."
          },
          "device_scale_factor": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "description": "Proporção de pixels do dispositivo (device pixel ratio) da captura de tela."
          },
          "wait_for_selector": {
            "type": "string",
            "description": "Aguarda até 10 segundos por este seletor CSS antes de capturar. Se ele não aparecer, a captura continua sem erro. Registrado em forensic_log.json page_modifications.",
            "maxLength": 200
          },
          "wait_ms": {
            "type": "number",
            "minimum": 0,
            "maximum": 15000,
            "description": "Espera extra em milissegundos depois que a página termina de carregar."
          },
          "screenshot_format": {
            "type": "string",
            "description": "jpeg entrega screenshot.jpeg; png entrega um screenshot.png sem perdas. As C2PA Content Credentials só são adicionadas a capturas de tela JPEG.",
            "enum": [
              "jpeg",
              "png"
            ],
            "default": "jpeg"
          },
          "url_stamp": {
            "type": "boolean",
            "description": "Adiciona uma faixa de cabeçalho acima da imagem da página mostrando a URL capturada, o horário UTC da captura (o momento em que o navegador produziu a captura de tela; para uma página longa capturada em faixas, o momento em que a última faixa foi produzida) e o ID da prova, identificada como anotação de software. A faixa é adicionada por cima da imagem, então nenhum pixel da página é coberto. O chain_of_custody.json registra screenshot_url_header.raw_screenshot_sha256 com raw_screenshot_source: para uma página capturada de uma só vez (browser_image, browser_png, browser_jpeg_joined, browser_jpeg_reencoded), o SHA-256 da imagem da página produzida pelo navegador, antes da faixa; para uma página longa capturada em faixas e unida (joined_bands, joined_bands_lossless, joined_bands_reencoded), o SHA-256 da imagem unida como o ProofSnap a gravou com a faixa, antes de qualquer C2PA Content Credentials. Quando a faixa não pode ser adicionada (com ela a imagem ultrapassaria os 65.535 px que um JPEG comporta, o desenho falhou ou, apenas no caminho alternativo de captura de tela, a imagem da página tem mais de 32.000.000 pixels), a captura de tela é entregue sem ela: o metadata.json registra isso em screenshot.url_stamp_skipped e o chain_of_custody.json em screenshot_url_header (applied false, com o motivo).",
            "default": false
          },
          "network_log": {
            "type": "boolean",
            "description": "Inclui network.har (HAR com cabeçalhos de requisição e resposta e tempos, sem os corpos) e network.original.har.",
            "default": true
          },
          "warc": {
            "type": "boolean",
            "description": "Inclui capture.warc (ISO 28500) com as transações HTTP do carregamento da página. Os corpos das respostas são lidos dentro de um limite de tempo (no máximo 30 s, e somente enquanto a parte opcional do orçamento de tempo da captura permitir, depois do tempo reservado para eIDAS, C2PA e a leitura final do relógio); um corpo cortado por esse limite é armazenado vazio com WARC-Truncated: time, uma resposta não alcançada fica de fora, e warc_captured em chain_of_custody.json (e warc em forensic_log.json) registra truncated, truncation_reason, response_pairs, records_total e as contagens.",
            "default": false
          },
          "external_archive": {
            "type": "boolean",
            "description": "Solicita ao serviço Save Page Now do Internet Archive que arquive a URL (o Internet Archive mantém essa cópia publicamente). O resultado é classificado conforme a requisição que produziu o snapshot e o momento dele em relação à requisição: created (feito para esta requisição), recent_existing (o Internet Archive respondeu com um snapshot anterior), created_after_request (existe um snapshot mais recente, mas a requisição não foi confirmada), existing (só existe um snapshot mais antigo), pending, rate_limited, failed (o Internet Archive recusou a requisição), unconfirmed (nenhuma resposta a confirmou ou recusou e nenhum snapshot é conhecido), unavailable, skipped, skipped_sensitive ou skipped_query. URLs que pareçam conter credenciais ou que tragam parâmetros, nomes de parâmetros ou segmentos de caminho semelhantes a tokens nunca são enviadas. Uma URL com query string só é enviada quando esta opção é definida explicitamente como true; com o padrão, ela é ignorada (skipped_query). O resultado é registrado em forensic_log.json (external_archive, com snapshot_offset_seconds em relação à captura) e em chain_of_custody.json, e retornado como external_archive por GET /captures/{capture_id}. Defina false para URLs privadas. A requisição é enviada assim que a página termina de carregar, em paralelo com o restante da captura. Uma falha não faz a captura falhar.",
            "default": true
          },
          "dismiss_consent": {
            "type": "boolean",
            "description": "Define previamente cookies de consentimento do tipo aceitar tudo antes de carregar a página e tenta fechar os banners de consentimento de cookies. Isso altera a página, por isso fica desativado por padrão; o que foi feito é registrado em forensic_log.json consent_dismissal.",
            "default": false
          },
          "dismiss_popups": {
            "type": "boolean",
            "description": "Fecha pop-ups de newsletter, promoção e sobreposições semelhantes pelo próprio controle de fechar. O número de pop-ups fechados e os controles usados ficam registrados em forensic_log.json page_modifications. Cada pop-up fechado fica registrado em forensic_log.json page_modifications (popups_dismissed, popups_dismissed_controls); o evidence.pdf informa quantos foram fechados.",
            "default": false
          },
          "dark_mode": {
            "type": "boolean",
            "description": "Renderiza com o esquema de cores escuro. Altera a aparência da página; registrado em forensic_log.json page_modifications. Informado no evidence.pdf e em forensic_log.json page_modifications.",
            "default": false
          },
          "block_ads": {
            "type": "boolean",
            "description": "Bloqueia requisições a hosts conhecidos de anúncios e rastreadores. Omite conteúdo; registrado em forensic_log.json page_modifications.",
            "default": false
          },
          "block_resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "image",
                "font",
                "media",
                "stylesheet"
              ]
            },
            "description": "Tipos de recurso a bloquear. Omite conteúdo; registrado em forensic_log.json page_modifications."
          },
          "inject_css": {
            "type": "string",
            "description": "CSS adicionado à página antes da captura. Altera a página; armazenado literalmente em forensic_log.json page_modifications. Sempre armazenado literalmente em forensic_log.json page_modifications (injected_css) com injected_css_status `applied` ou `failed` e injected_css_error. O evidence.pdf informa que CSS personalizado foi injetado; ele não imprime o código.",
            "maxLength": 10000
          },
          "inject_js": {
            "type": "string",
            "description": "JavaScript executado na página antes da captura. Altera a página. Sempre armazenado literalmente em forensic_log.json page_modifications (injected_js) com injected_js_status e injected_js_error: `applied` (foi executado até o fim), `invalid` (não compilou, por exemplo por um erro de sintaxe, então nunca foi executado e não alterou a página), `failed` (lançou um erro durante a execução, então pode ter alterado a página antes de parar) ou `timed_out` (a captura deixou de esperar por ele após 5 segundos; o script pode ter continuado a ser executado e a alterar a página). O evidence.pdf informa o resultado e remete ao forensic_log.json, e o certificado de procedência lista o código injetado entre as modificações da página, a menos que tenha sido `invalid`; nenhum dos dois imprime o código.",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "description": "Locale do navegador a emular, por exemplo de ou de-DE (define o idioma do navegador e o Accept-Language). Apenas emulação: nenhum proxy é usado, então o conteúdo que depende do IP do visitante continua refletindo a região do servidor de captura. Registrado em metadata.json geo.",
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "timezone": {
            "type": "string",
            "description": "Fuso horário do navegador a emular (nome IANA, por exemplo Europe/Berlin). Apenas emulação."
          },
          "language": {
            "type": "string",
            "description": "Idioma dos PDFs legíveis por pessoas. Um destes: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Maiúsculas, hífen e região são tolerados (\"DE\", \"fr-FR\" e \"PT-BR\" são aceitos); \"pt\" e \"zh\" sozinhos são rejeitados. Um valor não suportado retorna 400 MALFORMED_REQUEST.",
            "default": "en",
            "example": "de"
          },
          "timestamp_type": {
            "type": "string",
            "description": "Somente \"bitcoin\" é aceito (a ancoragem OpenTimestamps é sempre tentada). Qualquer outro valor retorna 400 INVALID_TIMESTAMP_TYPE. Use include_eidas para um carimbo do tempo qualificado.",
            "enum": [
              "bitcoin"
            ],
            "default": "bitcoin"
          },
          "include_eidas": {
            "type": "boolean",
            "description": "Solicita também um carimbo do tempo qualificado eIDAS (RFC 3161) sobre o SHA-256 do manifest.json, entregue como manifest.json.tsr (mais eidas_validation.json quando dados de validação são retornados). Somente chaves live: uma chave de teste retorna 403 EIDAS_REQUIRES_LIVE_KEY. Se a implantação não tiver serviço eIDAS configurado, a requisição é rejeitada com 503 EIDAS_UNAVAILABLE antes de qualquer cobrança. Antes de usar um token, o ProofSnap o verifica (assinatura, o hash que ele contém, a cadeia de certificados até a raiz CA Disig QCA4, o status do certificado da TSA) e registra essa verificação em eidas_validation.json (verification). Se a requisição do carimbo do tempo falhar durante a captura, ou se o token não passar nessa verificação, o pacote é entregue sem ele, apenas com a ancoragem OpenTimestamps, o carimbo não é cobrado e timestamps.eidas (linha da captura, GET /captures/{capture_id}, webhook completed) indica status not_obtained com o motivo (token_refused, unavailable ou time_budget). A partir da captura de tela, as etapas opcionais da captura (os corpos WARC, as respostas do arquivo externo e do WHOIS, a assinatura C2PA, a segunda leitura do relógio) reservam tempo para o carimbo e são as primeiras a serem cortadas. Cobrado por carimbo.",
            "default": false
          },
          "include_rule_902": {
            "type": "boolean",
            "description": "Adiciona o rule_902_certification.pdf: um modelo de certificação [FRE 902(13)/(14)](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-app-federalru-dup2-rule902.htm) e [28 U.S.C. 1746](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-partV-chap115-sec1746.htm) não assinado, para uma pessoa preencher e assinar. O SHA-256 dele é listado no manifest.json. Ele também é adicionado quando evidence_pack.custodian tem um valor não vazio. Se a geração falhar, a captura é concluída sem ele. Desativado no momento: o modelo de certificação está temporariamente desligado, então este campo é aceito e ignorado (nenhum rule_902_certification.pdf é gerado ou listado no pacote) e a resposta traz o aviso RULE902_DISABLED.",
            "default": false
          },
          "case_reference": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "O rótulo do seu caso ou processo. Espaços em branco são condensados. Segue a regra de texto simples (veja PlainText). Registrado em metadata.json, forensic_log.json e chain_of_custody.json, e impresso no evidence.pdf.",
            "maxLength": 200
          },
          "case_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "O identificador do caso no seu sistema. Registrado como case_reference; segue a regra de texto simples (veja PlainText).",
            "maxLength": 200
          },
          "case_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Nota em texto livre. Segue a regra de texto simples (veja PlainText). Registrada em metadata.json, forensic_log.json e chain_of_custody.json (legal_context.note).",
            "maxLength": 500
          },
          "webhook_url": {
            "type": "string",
            "description": "URL https que recebe capture.completed ou capture.failed. Veja Webhooks. A URL deve ser https e não pode apontar para um endereço privado, de loopback, link-local, de NAT de nível de operadora (CGNAT) ou de metadados de nuvem (as mesmas regras de uma URL de captura; caso contrário, 422 SSRF_BLOCKED). Um valor vazio ou só com espaços em branco significa nenhum webhook. Exige uma chave de API: enviado a partir de uma sessão do painel, é recusado com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave.",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "maxProperties": 10,
            "description": "Até 10 tags de string (valores de até 500 caracteres), retornadas com a captura. Violações retornam 400 METADATA_TOO_LARGE."
          },
          "evidence_pack": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "false deixa a página de certificação fora do evidence.pdf.",
                "default": true
              },
              "jurisdiction": {
                "type": "string",
                "description": "Registrado em forensic_log.json options. Não tem nenhum outro efeito.",
                "enum": [
                  "US-Federal",
                  "EU-eIDAS"
                ],
                "default": "US-Federal"
              },
              "custodian": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Aceito; não é impresso no modelo, cujos campos do declarante ficam em branco para a pessoa que o assina.",
                    "maxLength": 200
                  },
                  "law_firm": {
                    "type": "string",
                    "description": "Aceito; não é impresso no modelo, cujos campos do declarante ficam em branco para a pessoa que o assina.",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "description": "Aceito; não é usado no modelo.",
                    "maxLength": 200
                  },
                  "case_caption": {
                    "type": "string",
                    "description": "Aceito; não é usado no modelo.",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false,
                "description": "Todo valor segue a regra de texto simples (veja PlainText); caso contrário, 400 INVALID_EVIDENCE_PACK_OPTIONS."
              }
            },
            "description": "Valores inválidos retornam 400 INVALID_EVIDENCE_PACK_OPTIONS."
          }
        }
      },
      "CaptureRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Página a capturar. Somente http ou https, no máximo 2048 caracteres. Endereços privados, de loopback, link-local e de metadados são recusados com 422 SSRF_BLOCKED, e um hostname sem registros DNS retorna 422 TARGET_UNREACHABLE.",
            "format": "uri",
            "maxLength": 2048
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "$ref": "#/components/schemas/PrivateNote"
          }
        },
        "example": {
          "url": "https://example.com",
          "options": {
            "case_reference": "Smith v. Jones",
            "webhook_url": "https://your-app.example/webhooks/proofsnap"
          }
        }
      },
      "CaptureAccepted": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string",
            "example": "cap_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing"
            ]
          },
          "url": {
            "type": "string"
          },
          "poll_path": {
            "type": "string",
            "description": "Caminho de status incluindo o stage /v1, por exemplo /v1/captures/cap_..."
          },
          "message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "private_note": {
            "type": "string",
            "description": "A nota privada como armazenada (normalizada), presente somente quando uma foi enviada."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "CaptureResult": {
        "type": "object",
        "description": "Uma captura, certificado, comparação ou tarjamento. Os campos dependem do status.",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": "string",
            "description": "URL capturada (null para certificações e comparações).",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "type": {
            "type": "string",
            "description": "comparison em comparações, import em um pacote de provas importado.",
            "enum": [
              "comparison",
              "import"
            ]
          },
          "compared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Somente em comparações: os IDs das duas capturas de origem."
          },
          "diff_summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "estimated_completion_seconds": {
            "type": "integer",
            "description": "Apenas em processing: estimativa aproximada, no mínimo 5."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "CAPTURE_FAILED",
                  "CAPTURE_TIME_BUDGET_EXCEEDED",
                  "CAPTURE_SCREENSHOT_TIMEOUT",
                  "SSRF_BLOCKED",
                  "TARGET_UNREACHABLE",
                  "PAYMENT_REQUIRED",
                  "COMPARE_PROFILE_MISMATCH",
                  "COMPARE_URL_MISMATCH",
                  "RULE902_NOT_AVAILABLE_FOR_MISMATCH",
                  "COMPARE_SOURCE_INTEGRITY_FAILED",
                  "QUOTA_EXCEEDED",
                  "CERTIFY_FAILED",
                  "REDACTION_FAILED"
                ],
                "description": "O motivo público da falha (veja a tabela de erros). CAPTURE_SCREENSHOT_TIMEOUT: o navegador não concluiu a captura de tela; CAPTURE_TIME_BUDGET_EXCEEDED: a execução esgotou todo o seu orçamento de tempo; nenhum dos dois é cobrado. Uma linha de comparação (cmp_) também pode falhar com COMPARE_SOURCE_INTEGRITY_FAILED (um pacote de origem alterado depois que a requisição foi aceita), COMPARE_PROFILE_MISMATCH ou COMPARE_URL_MISMATCH (detectados apenas nos registros selados que a execução compara) ou RULE902_NOT_AVAILABLE_FOR_MISMATCH; a unidade é devolvida. Uma certificação de hash executada em segundo plano falha com QUOTA_EXCEEDED quando o máximo mensal já foi atingido no momento da execução. CERTIFY_FAILED e REDACTION_FAILED: uma certificação de hash ou um tarjamento não pôde ser concluído; repita a requisição."
              },
              "message": {
                "type": "string"
              }
            },
            "description": "Apenas failed."
          },
          "failed_at": {
            "type": "string",
            "description": "Apenas failed.",
            "format": "date-time"
          },
          "captured_at": {
            "type": "string",
            "description": "Quando a página foi capturada (somente linhas concluídas). Para um tarjamento, o horário de captura da sua captura de origem (redacted_at é quando o tarjamento foi feito).",
            "format": "date-time"
          },
          "redacted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Somente tarjamentos: quando o tarjamento foi feito. O captured_at de um tarjamento é o momento da captura de origem."
          },
          "compared_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Somente comparações: quando a comparação foi feita. Seu captured_at traz o mesmo valor (mantido para leitores mais antigos); use compared_at."
          },
          "package_url": {
            "type": "string",
            "description": "Somente completed: link de download pré-assinado do ZIP do pacote, válido por até 24 horas (veja package_url_expires_at); leia a captura novamente para obter um link novo.",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "description": "Somente completed: quando package_url deixa de funcionar, no máximo 24 horas após esta resposta (um link assinado com credenciais temporárias do servidor pode parar antes); leia a captura de novo para obter um link novo.",
            "format": "date-time"
          },
          "verification_url": {
            "type": "string",
            "description": "Apenas completed: link para o verificador público do ProofSnap.",
            "format": "uri"
          },
          "manifest_hash": {
            "type": "string",
            "description": "Somente completed: sha256:<hex> do manifest.json.",
            "example": "sha256:3f2a..."
          },
          "timestamps": {
            "$ref": "#/components/schemas/Timestamps"
          },
          "c2pa": {
            "type": "object",
            "properties": {
              "included": {
                "type": "boolean"
              },
              "signed_screenshot_sha256": {
                "type": "string",
                "description": "sha256:<hex> da captura de tela assinada.",
                "nullable": true
              }
            },
            "description": "Somente completed."
          },
          "file_count": {
            "type": "integer",
            "description": "Somente completed."
          },
          "size_bytes": {
            "type": "integer",
            "description": "Somente completed."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "consent_gated"
                  ]
                },
                "reason": {
                  "type": "string",
                  "nullable": true
                },
                "final_host": {
                  "type": "string",
                  "nullable": true
                },
                "final_url": {
                  "type": "string",
                  "nullable": true
                },
                "message": {
                  "type": "string"
                },
                "remedy": {
                  "type": "object",
                  "properties": {
                    "option": {
                      "type": "string"
                    },
                    "value": {
                      "type": "boolean"
                    }
                  }
                }
              }
            },
            "description": "Somente completed, quando a captura caiu em uma barreira de consentimento de cookies ou em uma página intersticial em vez da página. Execute novamente com dismiss_consent: true."
          },
          "external_archive": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExternalArchive"
              }
            ],
            "description": "Somente completed. A requisição ao Internet Archive feita com a opção external_archive: o resultado atual (status), o estado selado no pacote (status_at_sealing) e o link do snapshot. { requested: false } quando nenhuma requisição foi feita."
          },
          "storage": {
            "$ref": "#/components/schemas/CaptureStorage"
          },
          "private_note": {
            "type": "string",
            "nullable": true,
            "description": "Sua nota privada (veja PrivateNote). Retornada somente a quem fez a captura; nunca faz parte do pacote de provas."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "A captura está sob retenção legal: ela não é excluída enquanto for true (PATCH /captures/{capture_id} com action legal_hold). Liberada automaticamente, e a captura excluída, quando a assinatura terminou e seus 30 dias passaram sem um plano que mantenha os dados."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Quando a retenção legal atual foi aplicada; null sem retenção."
          },
          "legal_hold_reason": {
            "type": "string",
            "nullable": true,
            "description": "O motivo armazenado com a retenção legal atual; null sem retenção ou sem motivo."
          },
          "legal_hold_set_by_email": {
            "type": "string",
            "nullable": true,
            "description": "Somente o proprietário do escritório, em uma captura retida do escritório: o endereço de e-mail de quem aplicou a retenção."
          },
          "legal_hold_source": {
            "type": "string",
            "nullable": true,
            "description": "De onde vem a retenção atual: \"manual\" (PATCH /captures/{capture_id}), \"bulk\" (POST /legal-holds), \"matter:<matter_id>\" (a retenção legal do caso) ou \"derived:<capture_id>\" (um tarjamento ou comparação retido porque uma captura da qual foi feito está retida). null sem retenção."
          },
          "legal_hold_released_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Quando a última retenção foi liberada (início do período de carência da liberação); null enquanto houver retenção ou se nunca foi liberada, e após a liberação automática no fim de uma assinatura (sem carência)."
          },
          "deletion_blocked_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Fim do período de carência após uma liberação: até lá a captura não é excluída permanentemente (DELETE permanente 409 CAPTURE_HOLD_GRACE, sem esvaziamento da lixeira, sem exclusão por retenção); é possível movê-la para a lixeira. A exclusão da conta e do escritório após o fim da assinatura não espera por ele. null quando não há carência em curso (também enquanto uma retenção está ativa: ela bloqueia sem data de término)."
          },
          "s3_legal_hold": {
            "type": "object",
            "nullable": true,
            "description": "O legal hold do S3 Object Lock dos objetos do pacote armazenado (ZIP, prova OpenTimestamps atualizada e a cópia armazenada separadamente, todas as versões). status: on, off, pending (sendo aplicado), failed (o servidor tenta de novo), unsupported (o armazenamento não tem configuração de Object Lock; a retenção no registro da captura continua valendo), none (nenhum pacote armazenado, por exemplo uma captura com falha).",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "on",
                  "off",
                  "pending",
                  "failed",
                  "unsupported",
                  "none"
                ]
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "source_held": {
            "type": "boolean",
            "nullable": true,
            "description": "Tarjamentos e comparações: true quando uma captura a partir da qual foram feitos está sob retenção legal. Um pacote derivado segue a retenção da sua origem: enquanto uma origem estiver retida, o tarjamento ou a comparação também fica retido (legal_hold_source derived:<capture_id>) e a sua retenção não pode ser liberada isoladamente (409 LEGAL_HOLD_SOURCE). null para outras capturas."
          },
          "import": {
            "$ref": "#/components/schemas/CaptureImport"
          },
          "source": {
            "type": "string",
            "description": "Como a captura foi feita: api, schedule, certify-hash, compare, redact, import. GET /captures/{capture_id} o retorna apenas para um pacote importado (\"import\"); as linhas de GET /captures sempre o trazem."
          }
        }
      },
      "Timestamps": {
        "type": "object",
        "properties": {
          "bitcoin_ots": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "Estado da prova OpenTimestamps em Bitcoin em manifest.json.ots. pending_confirmation: a prova aguarda um bloco Bitcoin (geralmente algumas horas; as verificações começam cerca de 2 horas após a conclusão da captura e se repetem com intervalos crescentes). verified: um bloco Bitcoin atesta o hash do manifesto (block_height, block_time). not_present: o pacote não tem manifest.json.ots (os calendários não puderam ser alcançados quando o pacote foi selado). unconfirmed: nenhuma confirmação foi encontrada em 7 dias de verificação; a prova no pacote ainda pode ser atualizada e verificada com o cliente OpenTimestamps (ots upgrade, ots verify). package_missing: o pacote de provas não está mais no armazenamento, então a prova não pode mais ser verificada. manifest_mismatch: o manifest.json no pacote armazenado não corresponde ao hash do manifesto registrado quando ele foi selado; o carimbo do tempo não está confirmado para ele. Ambos são finais. O pacote em si nunca é alterado. not_checked: a captura foi concluída antes de existirem verificações automáticas de confirmação e nunca foi verificada; seu pacote ainda pode conter um manifest.json.ots que o cliente OpenTimestamps consegue atualizar e verificar. As respostas de webhook e de criação são enviadas na conclusão, por isso indicam pending_confirmation (ou not_present quando os calendários falharam); leia GET /captures/{capture_id} para o estado atual. Um pacote é selado com a prova pendente: nesse momento, o hash do manifesto já foi enviado aos calendários OpenTimestamps. Uma vez confirmada no bloco Bitcoin {block_height} ({block_time} UTC), a prova mostra que manifest.json existia no mais tardar naquele momento; baixe a prova atualizada (upgraded_proof_url) e coloque-a ao lado de manifest.json como manifest.json.upgraded.ots, e verify.sh e verify.ps1 a verificam automaticamente.",
                "enum": [
                  "pending_confirmation",
                  "verified",
                  "not_present",
                  "unconfirmed",
                  "package_missing",
                  "manifest_mismatch",
                  "not_checked"
                ]
              },
              "block_height": {
                "type": "integer",
                "nullable": true,
                "description": "Altura do bloco Bitcoin que atesta a prova. Definida quando status é verified."
              },
              "block_time": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Horário desse bloco Bitcoin. Somente em GET /captures/{capture_id}, definido quando status é verified."
              },
              "verified_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Quando ProofSnap confirmou a ancoragem. Somente em GET /captures/{capture_id}, preenchido quando status é verified."
              },
              "upgraded_proof_url": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Somente GET /captures/{capture_id}. Depois que a ancoragem é verificada, o ProofSnap armazena a prova OpenTimestamps atualizada a partir dos calendários (ela contém a atestação do bloco Bitcoin, portanto é verificada offline sem contatar nenhum calendário). Este é um download pré-assinado dela, válido por até 24 horas, como package_url; leia a captura novamente para obter um link novo. null quando não há prova (ainda não verificada, ou armazenada mais tarde por um preenchimento retroativo em segundo plano). O pacote selado nunca é alterado: salve o arquivo ao lado do manifest.json do pacote como manifest.json.upgraded.ots. Pacotes selados a partir de 2026-09-29 a verificam com seus scripts incluídos (verification/verify.sh e verify.ps1 a usam no lugar de manifest.json.ots); para um pacote mais antigo, execute `ots verify manifest.json.upgraded.ots -f manifest.json`. As exportações de casos a trazem como captures/<capture_id>.upgraded.ots ao lado de captures/<capture_id>.zip. Excluída junto com o pacote."
              }
            }
          },
          "eidas": {
            "$ref": "#/components/schemas/EidasTimestamp"
          }
        }
      },
      "DiffSummary": {
        "type": "object",
        "properties": {
          "text_insertions": {
            "type": "integer",
            "description": "Linhas adicionadas (o mesmo valor de text_lines_added; desde 2026-10-14 o texto é contado em linhas, veja text_unit). Comparações feitas antes contam caracteres. Obsoleto (2026-10-14): mantido para leitores existentes, não removido; use text_lines_added.",
            "deprecated": true
          },
          "text_deletions": {
            "type": "integer",
            "description": "Linhas removidas (o mesmo valor de text_lines_removed; linhas desde 2026-10-14, caracteres antes disso). Obsoleto (2026-10-14): mantido para leitores existentes, não removido; use text_lines_removed.",
            "deprecated": true
          },
          "text_unit": {
            "type": "string",
            "enum": [
              "line"
            ],
            "description": "A unidade das contagens de texto: line (cada linha de domtextcontent.txt com a sua quebra de linha). Ausente em comparações feitas antes de 2026-10-14, que contavam caracteres."
          },
          "text_lines_added": {
            "type": "integer",
            "description": "Linhas adicionadas (linhas do texto After nos trechos adicionados do diff de linhas)."
          },
          "text_lines_removed": {
            "type": "integer",
            "description": "Linhas removidas (linhas do texto Antes em trechos removidos do diff de linhas)."
          },
          "text_lines_before": {
            "type": "integer",
            "description": "Linhas do texto Antes."
          },
          "text_lines_after": {
            "type": "integer",
            "description": "Linhas do texto After."
          },
          "text_changed_percent": {
            "type": "number",
            "description": "100 x (linhas removidas + linhas adicionadas) / (linhas antes + linhas depois), com uma casa decimal."
          },
          "text_diff_capped": {
            "type": "boolean",
            "description": "true quando o diff de linhas atingiu seu orçamento de trabalho e uma parte dos textos foi contada como substituída por inteiro (superestima, nunca oculta uma mudança)."
          },
          "metadata_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "{ key, before, after, class } por campo do metadata.json que difere, exatamente estes: url, final_url, page_title, http_status, http_headers.content-type, http_headers.server, dns.connected_ip, resolved_ips (comparados como lista ordenada), tls.issuer, tls.valid_to, e os campos do perfil de dispositivo viewport_width, viewport_height, device_scale_factor, is_mobile e user_agent. class é content (url, final_url, page_title, http_status, http_headers.content-type: contado como alteração), infrastructure (http_headers.server, dns.connected_ip, resolved_ips, tls.issuer, tls.valid_to: qual servidor entregou a página, apenas informado) ou device_profile (como a captura renderizou a página, apenas informado). Uma comparação feita antes de 2026-10-10 não tem class."
          },
          "meta_content": {
            "type": "integer",
            "description": "Campos de metadados divergentes da classe content; contam para changed."
          },
          "meta_infra": {
            "type": "integer",
            "description": "Campos de metadados divergentes da classe infraestrutura (software do servidor, IP conectado, IPs resolvidos, emissor e validade do TLS). Uma rede de distribuição de conteúdo responde a partir de endereços rotativos, então dois carregamentos de uma página inalterada podem divergir aqui; informado a título de referência, nunca contado para changed."
          },
          "meta_device_profile": {
            "type": "integer",
            "description": "Campos do perfil de dispositivo que diferem (viewport, escala, emulação mobile, user agent); informados a título informativo, nunca contados em changed."
          },
          "image_changes": {
            "type": "object",
            "description": "A comparação de imagens, a partir do inventário de imagens que cada captura de origem selou (image_inventory.json). status ran, ou not_run com reason source_without_inventory (uma origem capturada antes de as capturas registrarem um), source_inventory_failed ou source_inventory_unreadable; nada mais é usado no lugar dele. Os resultados completos por imagem ficam em image_changes.json do pacote.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ran",
                  "not_run"
                ]
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "source_without_inventory",
                  "source_inventory_failed",
                  "source_inventory_unreadable",
                  null
                ],
                "description": "Por que as imagens não foram comparadas: source_without_inventory (uma origem feita antes de as capturas registrarem um inventário de imagens; capturas novas registram um), source_inventory_failed (o inventário não pôde ser registrado quando aquela captura foi feita) ou source_inventory_unreadable; null quando a comparação foi executada."
              },
              "images_changed": {
                "type": "integer",
                "nullable": true,
                "description": "Imagens próprias (servidas a partir do domínio registrável da própria página, ou desde 2026-10-14 a partir de um domínio que a página declara como seu og:image ou ícone, ou que serve a maioria das imagens da página nas duas capturas: image_changes.json site_image_hosts) substituídas, adicionadas ou removidas, a parte de imagens de changed; imagens movidas não são incluídas. null quando a comparação não foi executada. Obsoleto (2026-10-14): mantido para leitores existentes, não removido; use site_images_changed.",
                "deprecated": true
              },
              "first_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "third_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "tracking_pixel": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "inventories_truncated": {
                "type": "boolean",
                "description": "true quando um inventário de origem atingiu seu limite de entradas; veja inventory_cutoff."
              },
              "site_images_changed": {
                "type": "integer",
                "description": "O mesmo que images_changed (contadas)."
              },
              "other_images_changed": {
                "type": "integer",
                "description": "Imagens de outros domínios e pixels de rastreamento substituídos, removidos ou adicionados (nunca contados)."
              },
              "site_image_hosts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Domínios registráveis considerados próprios do site além do domínio da página (site_image_hosts de image_changes.json)."
              },
              "inventory_cutoff": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "before_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Uma imagem encontrada apenas em Before é determinável (removed) quando seu índice no inventário de Before é menor que este valor; null: sem limite (o inventário de After está completo)."
                  },
                  "after_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Uma imagem encontrada apenas em After é determinável (added) quando seu índice no inventário de After é menor que este valor; null: sem limite (o inventário de Before está completo)."
                  }
                },
                "description": "Desde a segunda auditoria de 2026-10-14 (image_changes.json algorithm 3), um objeto, decidido pelo índice no documento: quando o OUTRO inventário atingiu o seu limite de entradas, uma imagem encontrada em apenas um lado só pode ser decidida quando o seu índice está abaixo da contagem de entradas do outro inventário menos uma margem (o maior valor entre 10 entradas e 5 por cento dessa contagem); caso contrário, é indeterminada (undetermined_reason beyond_inventory_limit). null quando nenhum inventário está truncado. Comparações feitas antes trazem um número (a posição relativa no documento da regra anterior)."
              }
            }
          },
          "changed": {
            "type": "boolean",
            "nullable": true,
            "description": "true quando o texto visível difere, um campo de metadados de conteúdo difere (meta_content) ou uma imagem própria foi substituída, removida ou adicionada (image_changes.images_changed). Campos de infraestrutura e de perfil de dispositivo, imagens movidas (os mesmos bytes em outro endereço), imagens de terceiros e pixels de rastreamento são informados, mas não o definem. null (desde 2026-10-14) quando nada contabilizado mudou de forma confiável, mas o resultado do texto ou das imagens é indeterminado (capturas renderizadas de forma diferente, allow_profile_mismatch). O pacote declara a regra em comparison_method.json counted_as_change. Obsoleto (2026-10-14): mantido para leitores existentes, não removido; use verdict (changed | unchanged | undetermined) e text_verdict, images_verdict, metadata_verdict por tipo.",
            "deprecated": true
          },
          "text_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "undetermined quando as duas capturas foram renderizadas com um perfil de dispositivo ou configurações de captura diferentes (allow_profile_mismatch)."
          },
          "images_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined",
              "not_run"
            ]
          },
          "metadata_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged"
            ],
            "description": "A partir dos campos de metadados da classe conteúdo (endereço, endereço final, título, status HTTP, content type)."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "O resultado geral em palavras: changed quando changed é true, undetermined quando changed é null."
          },
          "undetermined_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "render_profile_mismatch",
              "capture_settings_mismatch",
              null
            ]
          },
          "text_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Somente GET: até 60 trechos de texto alterados."
          },
          "change_regions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Somente GET: até 60 alterações de texto localizadas nas capturas de tela (a partir do text_layout.json das origens), em pixels da captura de tela. Cada alteração traz counterpart_y_px (sua posição vertical na outra captura de tela, transferida do bloco de texto inalterado mais próximo) e report_region (o número do local de alteração em que o evidence.pdf a mostra, no máximo 10 locais, ou null), como em change_regions.json."
          },
          "change_regions_info": {
            "type": "object",
            "nullable": true
          },
          "order_reversed": {
            "type": "boolean",
            "description": "Somente GET: true quando a captura informada como capture_id_1 (Before) foi feita depois da informada como capture_id_2 (After), como order_reversed em compare.json. A comparação mantém a ordem informada; o relatório e o painel indicam isso."
          },
          "render_profile_mismatch": {
            "type": "boolean",
            "description": "Somente GET: as duas capturas foram renderizadas com um perfil de dispositivo diferente (compare.json)."
          },
          "capture_settings_mismatch": {
            "type": "boolean",
            "description": "Somente GET: as duas capturas foram feitas com configurações de captura diferentes (compare.json)."
          },
          "capture_settings_differing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "setting": {
                  "type": "string"
                },
                "before": {},
                "after": {}
              }
            },
            "description": "Somente GET: as configurações que diferem, { setting, before, after }."
          },
          "profile_mismatch_allowed": {
            "type": "boolean",
            "description": "Somente GET: a comparação foi feita com allow_profile_mismatch para capturas renderizadas de forma diferente. Obsoleto (2026-10-14): mantido para leitores existentes, não removido; use render_profile_mismatch e capture_settings_mismatch.",
            "deprecated": true
          },
          "url_mismatch": {
            "type": "boolean",
            "description": "Somente GET: as duas capturas não têm nenhum endereço em comum (feita com allow_url_mismatch)."
          }
        },
        "description": "Resumo da comparação."
      },
      "ImageChangeCounts": {
        "type": "object",
        "properties": {
          "unchanged": {
            "type": "integer"
          },
          "replaced": {
            "type": "integer"
          },
          "moved": {
            "type": "integer"
          },
          "removed": {
            "type": "integer"
          },
          "added": {
            "type": "integer"
          },
          "undetermined": {
            "type": "integer"
          },
          "alt_changed": {
            "type": "integer"
          },
          "position_changed": {
            "type": "integer"
          },
          "undetermined_same_error": {
            "type": "integer",
            "description": "Pares indeterminados cujos dois lados retornaram o mesmo status de erro HTTP (por exemplo, 404 nas duas capturas); incluídos em undetermined, contados no relatório e não listados."
          },
          "occurrences": {
            "type": "integer",
            "description": "Todos os resultados desta parte: ocorrências na página."
          },
          "distinct": {
            "type": "object",
            "description": "O número de imagens diferentes entre os resultados, no total e por categoria (uma imagem exibida várias vezes conta uma vez): cada resultado representa o SHA-256 do seu lado Depois, ou do seu lado Antes quando não tem lado Depois, ou o endereço desse lado quando ele não traz SHA-256.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "replaced": {
                "type": "integer"
              },
              "moved": {
                "type": "integer"
              },
              "removed": {
                "type": "integer"
              },
              "added": {
                "type": "integer"
              },
              "undetermined": {
                "type": "integer"
              }
            }
          }
        },
        "description": "Contagens por categoria, como ocorrências na página (uma imagem exibida três vezes conta três vezes): unchanged, replaced (mesmo endereço, bytes diferentes), moved (endereço diferente, mesmos bytes; informado, não contado como alteração), removed, added, undetermined (uma imagem sem SHA-256 em nenhum dos lados, nunca contada como removida ou adicionada); além dos pares de imagens cujo texto alternativo ou posição entre as imagens da página mudou, dos pares indeterminados com o mesmo erro HTTP nos dois lados e das imagens distintas."
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "minItems": 2,
            "maxItems": 50,
            "description": "De 2 a 50 URLs, cada uma validada como CaptureRequest.url. Os nomes de host não passam por verificação de DNS quando o lote é aceito; uma URL que falha na verificação de DNS ou de endereço falha depois, com status failed (e capture.failed)."
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PrivateNote"
              }
            ],
            "description": "Uma nota privada armazenada em todas as capturas do lote. Sua nota privada sobre a captura (por exemplo \"captura no celular\"), no máximo 200 caracteres, texto simples (a regra PlainText). Ela é armazenada apenas no registro da captura: NÃO faz parte do pacote de provas selado (nem do ZIP, do PDF, do manifesto ou de qualquer hash) e não tem valor probatório, então pode ser alterada ou apagada depois com PATCH /v1/captures/{capture_id} sem afetar a prova. Só quem fez a captura a lê. Omita-a, ou envie null ou uma string vazia, para não ter nota."
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "example": "batch_a1B2c3D4e5F6g7H8"
          },
          "count": {
            "type": "integer"
          },
          "captures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "capture_id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "failed quando a captura não pôde ser iniciada.",
                  "enum": [
                    "processing",
                    "failed"
                  ]
                }
              }
            }
          },
          "not_admitted": {
            "type": "array",
            "description": "Somente quando o limite de capturas simultâneas da conta foi atingido: as URLs que não foram iniciadas, na ordem enviada. Elas não custam nada (nenhuma unidade de limite de requisições ou mensal); envie-as novamente mais tarde. Ausente quando todas as URLs foram iniciadas.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "description": "Por que a URL não foi iniciada.",
                  "enum": [
                    "CONCURRENT_CAPTURE_LIMIT"
                  ]
                }
              }
            }
          },
          "in_flight_limit": {
            "type": "integer",
            "description": "Apenas junto com not_admitted: o número de capturas que a conta pode ter em execução ao mesmo tempo (developer 10, startup 25, growth 100, legal_solo e legal_seats 25, custom 200, website_archive 25, sandbox 5)."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "nullable": true,
            "description": "match: assinatura válida e cada arquivo listado presente com o SHA-256 registrado. modified: assinatura inválida ou um arquivo ausente ou alterado. invalid: o pacote não pôde ser verificado. null apenas para um pacote importado que não foi verificado de novo desta vez (`rechecked: false`, com `reason` e a verificação registrada na importação em `stored_verification`).",
            "enum": [
              "match",
              "modified",
              "invalid",
              null
            ]
          },
          "reason": {
            "type": "string",
            "description": "Somente invalid; para um pacote importado com `rechecked: false`, por que ele não foi verificado novamente (too_large_for_synchronous_check, time_budget, storage_error)."
          },
          "capture_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "s3"
            ]
          },
          "checks": {
            "type": "object",
            "properties": {
              "manifest_signature": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "valid",
                      "invalid"
                    ]
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "RSA-4096"
                    ]
                  }
                }
              },
              "declared_files": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "all_match",
                      "mismatch"
                    ]
                  },
                  "mismatched": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Apenas a presença de manifest.json.ots; a prova não é verificada aqui.",
                    "enum": [
                      "present",
                      "not_present"
                    ]
                  }
                }
              },
              "eidas_disig": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Presença de manifest.json.tsr. O token em si é verificado em checks.eidas. missing: manifest.json registra eidas_requested (o pacote foi emitido com o token), mas manifest.json.tsr não está no pacote.",
                    "enum": [
                      "present",
                      "missing"
                    ]
                  }
                },
                "nullable": true,
                "description": "null quando não há manifest.json.tsr e o manifest.json não registra eidas_requested."
              },
              "proofsnap_issuance": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "issued_by_proofsnap: o hash do manifesto está no registro de emissões do ProofSnap. unverified: não está (pacotes emitidos antes da existência do registro não estão nele). unknown: a consulta falhou.",
                    "enum": [
                      "issued_by_proofsnap",
                      "unverified",
                      "unknown"
                    ]
                  },
                  "manifest_sha256": {
                    "type": "string"
                  },
                  "issued_at": {
                    "type": "string",
                    "nullable": true,
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string"
                  }
                }
              },
              "eidas": {
                "type": "object",
                "nullable": true,
                "description": "null quando não há manifest.json.tsr e o manifest.json não registra eidas_requested. O token do carimbo do tempo eIDAS conforme verificado pelo ProofSnap, sob demanda, a partir do pacote armazenado. Esta é uma verificação feita pelo ProofSnap; o campo note explica como verificar o token de forma independente.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "verified_revocation_unknown",
                      "failed",
                      "unavailable",
                      "missing"
                    ],
                    "description": "missing: o manifest.json registra eidas_requested: true, então o pacote foi emitido com um carimbo do tempo qualificado, mas o manifest.json.tsr não está nele (a resposta então traz o aviso EIDAS_TOKEN_MISSING). verified: todas as verificações passaram e o status do certificado da TSA registrado na emissão é conhecido (good, ou revoked após o horário do token por um motivo que não seja comprometimento da chave). verified_revocation_unknown: todas as verificações executadas passaram, mas não foi possível determinar o status do certificado da TSA a partir dos dados registrados (revocation.status unknown ou not_checked). failed: pelo menos uma verificação falhou, veja failures (a resposta então traz o aviso EIDAS_TOKEN_FAILED). unavailable: nenhum veredito sobre o token (reason: not configured, timeout, input_rejected quando o verificador recusou a entrada, por exemplo dados de validação grandes demais, inconclusive); nada se conclui sobre o token."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Somente unavailable: timeout, input_rejected, inconclusive ou uma breve explicação."
                  },
                  "checked_by": {
                    "type": "string",
                    "example": "ProofSnap API on request"
                  },
                  "checked_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "gen_time": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "O momento registrado no token (TSTInfo genTime)."
                  },
                  "tsa_name": {
                    "type": "string",
                    "nullable": true,
                    "description": "Do certificado do signatário (nome comum e organização), nunca um valor padrão."
                  },
                  "serial_number": {
                    "type": "string",
                    "nullable": true
                  },
                  "policy_oid": {
                    "type": "string",
                    "nullable": true
                  },
                  "hash_algorithm": {
                    "type": "string",
                    "nullable": true
                  },
                  "signature_algorithm": {
                    "type": "string"
                  },
                  "signing_certificate_form": {
                    "type": "string",
                    "enum": [
                      "ESSCertIDv2",
                      "ESSCertID"
                    ]
                  },
                  "signer_certificate": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "subject": {
                        "type": "string"
                      },
                      "issuer": {
                        "type": "string"
                      },
                      "serial_number": {
                        "type": "string"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "not_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "not_after": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  },
                  "root": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string",
                        "example": "CA Disig QCA4"
                      },
                      "sha256": {
                        "type": "string"
                      }
                    }
                  },
                  "chain_checked_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "O momento em relação ao qual a cadeia foi verificada: o genTime do token, de modo que um certificado que expirou depois da emissão do token não o invalida."
                  },
                  "chain": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "sha256": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "revocation": {
                    "type": "object",
                    "description": "Status do certificado da TSA a partir dos dados registrados na emissão (base recorded_at_issuance), verificado pela assinatura e que precisa ter estado vigente no genTime. Não é consultado em tempo real.",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "good",
                          "revoked",
                          "revoked_after_gen_time",
                          "unknown",
                          "not_checked"
                        ],
                        "description": "revoked_after_gen_time: revogado após o horário do token por um motivo diferente de comprometimento da chave (o token continua verificado). not_checked: nenhum dado de revogação foi registrado ou verificado."
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "ocsp",
                          "crl"
                        ],
                        "nullable": true
                      },
                      "basis": {
                        "type": "string",
                        "enum": [
                          "recorded_at_issuance"
                        ]
                      },
                      "produced_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "this_update": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "next_update": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      },
                      "reason": {
                        "type": "string",
                        "description": "Por que o status é unknown."
                      }
                    }
                  },
                  "checks": {
                    "type": "object",
                    "description": "Cada verificação: passed, failed, not_applicable (nonce: nenhum registrado para um token armazenado), unknown (revogação sem dados utilizáveis) ou not_checked (uma falha anterior interrompeu a verificação).",
                    "properties": {
                      "response_status": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_imprint": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "nonce": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "content_type": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_digest": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signature": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signing_certificate": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "timestamping_eku": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "chain": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "revocation": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      }
                    }
                  },
                  "failures": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "check": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string",
                    "nullable": true,
                    "description": "Somente input_rejected: o que o verificador recusou."
                  }
                }
              }
            },
            "description": "Ausente quando verdict é invalid. O veredito depende apenas de manifest_signature e declared_files; eidas é informado ao lado dele e, quando falhou, em warnings."
          },
          "warnings": {
            "type": "array",
            "description": "Presente somente quando algo fora do veredito exige atenção. EIDAS_TOKEN_FAILED: manifest.json.tsr não passou na verificação em checks.eidas. EIDAS_TOKEN_MISSING: manifest.json registra eidas_requested, mas manifest.json.tsr não está no pacote.",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "EIDAS_TOKEN_FAILED",
                    "EIDAS_TOKEN_MISSING"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": [
          "url",
          "mode"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Página a capturar, validada como CaptureRequest.url.",
            "format": "uri",
            "maxLength": 2048
          },
          "mode": {
            "type": "string",
            "description": "interval: a cada interval_seconds. cron: conforme cron_expr. on_change: verifica a página a cada interval_seconds e captura apenas quando o texto dela mudou.",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "description": "Obrigatório para interval e on_change. No mínimo o mínimo do plano (300 em developer, startup, growth, custom, legal_solo e legal_seats; 86400 em website_archive); caso contrário, 400 INTERVAL_TOO_SHORT.",
            "minimum": 300
          },
          "cron_expr": {
            "type": "string",
            "description": "Obrigatório para cron (no máximo 200 caracteres). Uma expressão que não pode ser interpretada retorna 400 INVALID_CRON; duas execuções consecutivas mais próximas que o mínimo do plano retornam 400 INTERVAL_TOO_SHORT.",
            "maxLength": 200,
            "example": "0 9 * * 1-5"
          },
          "timezone": {
            "type": "string",
            "description": "Fuso horário IANA usado para avaliar cron_expr. Padrão UTC.",
            "example": "Europe/Berlin"
          },
          "webhook_url": {
            "type": "string",
            "description": "URL https. Execuções interval e cron enviam capture.completed ou capture.failed; on_change envia monitor.change_detected. A URL deve ser https e não pode apontar para um endereço privado, de loopback, link-local, de NAT de nível de operadora ou de metadados de nuvem (as mesmas regras de uma URL de captura; caso contrário, 422 SSRF_BLOCKED). Um valor vazio ou só com espaços significa nenhum webhook. Um agendamento tem um único webhook_url, no nível superior: um options.webhook_url não é armazenado (quando nenhum webhook_url de nível superior é enviado, ele é usado como o de nível superior), portanto um agendamento on_change nunca envia também capture.completed. Exige uma chave de API: enviado de uma sessão do painel, é recusado com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave.",
            "format": "uri"
          },
          "js_render": {
            "type": "boolean",
            "description": "Somente on_change: verifica a página com uma renderização completa no navegador em vez de uma simples busca HTTP (para páginas que precisam de JavaScript).",
            "default": false
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true,
            "minimum": 86400,
            "maximum": 34560000,
            "description": "Somente on_change: também faz uma captura completa (ela envia capture.completed) quando a captura mais recente do agendamento é mais antiga que isto, mesmo que a página não tenha mudado. Para regras que exigem uma cópia pelo menos a cada N dias, por exemplo 7776000 para 90 dias. De 1 dia a 400 dias e maior que interval_seconds; null (padrão) captura somente quando há alteração. PATCH com null o remove."
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "Somente on_change (2026-10-14): qual alteração detectada envia monitor.change_detected. any (padrão, o comportamento anterior): toda captura de alteração. text: somente quando a comparação com a captura anterior constata que o texto visível mudou (diff_summary.text_verdict changed); uma captura de alteração cuja comparação diz que o texto não mudou ou que não foi possível determinar não envia nada (a captura continua sendo feita, cobrada e envia capture.completed às assinaturas). Sem resultado de comparação (sem linha de base, a comparação falhou), ela alerta. 400 MALFORMED_REQUEST com outro modo ou valor. null para agendamentos interval e cron."
          }
        },
        "example": {
          "url": "https://example.com/terms",
          "mode": "on_change",
          "interval_seconds": 3600,
          "webhook_url": "https://your-app.example/webhooks/proofsnap"
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "schedule_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "nullable": true
          },
          "cron_expr": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "webhook_url": {
            "type": "string",
            "nullable": true,
            "description": "O webhook do agendamento (para um agendamento mais antigo que o mantinha em options.webhook_url, esse valor é exibido aqui e options nunca o contém). Exige uma chave de API: enviado a partir de uma sessão do painel, é recusado com 400 WEBHOOK_REQUIRES_API_KEY, porque os webhooks são assinados com o segredo de webhook da chave."
          },
          "options": {
            "type": "object",
            "description": "Opções de captura com os valores padrão aplicados."
          },
          "js_render": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          },
          "next_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_run_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "last_capture_id": {
            "type": "string",
            "nullable": true
          },
          "run_count": {
            "type": "integer"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "pause_reason": {
            "type": "string",
            "description": "Por que o agendamento foi pausado automaticamente: max_failures (5 execuções seguidas com falha), no_billing, plan_downgrade (limites do plano, ou um plano Archive only), invalid_cron ou key_revoked. null quando ativo ou pausado por você.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true
          },
          "current_capture_id": {
            "type": "string",
            "nullable": true,
            "description": "A captura COMPLETED mais recente do agendamento (sua versão atual; last_capture_id é a mais recente despachada)."
          },
          "current_captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change: any ou text (any para um agendamento criado antes da opção); null para interval e cron."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "Matter": {
        "type": "object",
        "properties": {
          "matter_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "client": {
            "type": "string",
            "nullable": true
          },
          "case_number": {
            "type": "string",
            "nullable": true
          },
          "opposing_party": {
            "type": "string",
            "nullable": true
          },
          "jurisdiction": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "archived",
              "sealed"
            ]
          },
          "sealed": {
            "type": "boolean"
          },
          "capture_count": {
            "type": "integer"
          },
          "custody_count": {
            "type": "integer",
            "description": "Entradas no registro de custódia."
          },
          "custody_head_hash": {
            "type": "string",
            "description": "sha256:<hex> da entrada mais recente do log de custódia.",
            "nullable": true
          },
          "seal": {
            "type": "object",
            "nullable": true,
            "description": "null até ser selado.",
            "allOf": [
              {
                "$ref": "#/components/schemas/MatterSeal"
              }
            ]
          },
          "member_uids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_by": {
            "type": "string",
            "description": "O proprietário.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_only": {
            "type": "boolean",
            "description": "Presente (true) em um caso de um escritório que foi encerrado ou é mantido como company archive: apenas abrir, exportar e revogar o compartilhamento."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "A retenção legal do caso está ativa (PATCH { legal_hold })."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MatterSeal": {
        "type": "object",
        "properties": {
          "root_hash": {
            "type": "string",
            "description": "sha256:<hex>: o head de custódia que foi selado e enviado ao OpenTimestamps."
          },
          "ots": {
            "type": "string",
            "description": "Prova OpenTimestamps, em hex."
          },
          "ots_pending": {
            "type": "boolean"
          },
          "eidas": {
            "type": "object",
            "properties": {
              "tsr_base64": {
                "type": "string",
                "nullable": true
              },
              "serial": {
                "type": "string",
                "nullable": true
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "ltv": {
                "type": "boolean"
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "verified"
                ],
                "description": "O token passou na verificação do ProofSnap no momento da selagem (verificador compartilhado, mesmas verificações de um token de captura)."
              },
              "revocation_status": {
                "type": "string",
                "enum": [
                  "good",
                  "revoked_after_gen_time",
                  "unknown",
                  "not_checked"
                ],
                "description": "Status do certificado da TSA estabelecido no momento da selagem a partir da resposta OCSP ou da CRL coletada naquele momento."
              },
              "validation": {
                "type": "object",
                "nullable": true,
                "description": "O conteúdo de eidas_validation.json registrado na selagem, para uma verificação offline do token: tsaCertChain, rootCaPem, ocspResponse, crlResponse (removidos e listados em omitted quando grandes demais), ocspUrl, ocspFetchedAt, ocspStatus, crlUrl, verification. Selos feitos antes de 2026-10-01 não têm esse conteúdo.",
                "additionalProperties": true
              }
            },
            "nullable": true,
            "description": "Carimbo do tempo eIDAS sobre o mesmo hash quando a implantação tem o eIDAS configurado, o chamador é de produção e um token foi obtido e passou na verificação; caso contrário, null (veja eidas_status)."
          },
          "sealed_by": {
            "type": "string",
            "nullable": true
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time"
          },
          "eidas_status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained",
              "not_configured"
            ],
            "description": "Ausente nos selos feitos antes de 2026-10-01. not_obtained: o selo traz apenas a ancoragem OpenTimestamps (eidas_reason token_refused, unavailable ou test_key). Um timeout do carimbo eIDAS nunca gera um selo: a requisição retorna 503 EIDAS_SEAL_RETRY."
          },
          "eidas_reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "test_key",
              "account_limit"
            ],
            "description": "Apenas not_obtained. test_key: selado por quem chamou em modo de teste (chave de API de teste ou sessão sandbox), então nenhum carimbo do tempo eIDAS foi solicitado. account_limit: a conta ainda não tem nenhuma fatura paga e atingiu seu limite de eIDAS (20 por dia UTC, 50 por mês UTC)."
          }
        },
        "nullable": true
      },
      "CustodyEntry": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "enum": [
              "api",
              "user"
            ],
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Por exemplo matter.created, matter.updated, capture.added, capture.removed, capture.legal_hold_placed, capture.legal_hold_released, member.added, member.removed, package.imported (um pacote de provas importado com matter_id; target é o id da captura)."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string"
          },
          "entry_hash": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Somente package.imported: manifest_sha256, zip_sha256, package_kind, verdict, levels. Armazenado com a entrada, mas NÃO coberto por entry_hash.",
            "additionalProperties": true
          }
        },
        "description": "Um elo da cadeia de hashes. entry_hash abrange seq, ts, actor, action, target e prev_hash."
      },
      "MatterCapture": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string"
          },
          "captured_at": {
            "type": "string",
            "nullable": true
          },
          "file_count": {
            "type": "integer",
            "nullable": true
          },
          "manifest_hash": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "ts": {
            "type": "string"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Por exemplo capture.downloaded, export.generated, share.created, share.revoked."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "source_ip": {
            "type": "string",
            "nullable": true,
            "description": "Somente o proprietário do caso (ausente para membros)."
          },
          "user_agent": {
            "type": "string",
            "nullable": true,
            "description": "Somente o proprietário do caso (ausente para membros)."
          }
        },
        "description": "Evento de acesso. Não faz parte da cadeia de hashes."
      },
      "CertifyResult": {
        "type": "object",
        "properties": {
          "certify_id": {
            "type": "string",
            "example": "cert_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "hash_sha256": {
            "type": "string",
            "description": "Somente certificado único."
          },
          "combined": {
            "type": "boolean",
            "description": "Somente o certificado combinado."
          },
          "file_count": {
            "type": "integer",
            "description": "Somente o certificado combinado."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "O rótulo que você enviou.",
                  "nullable": true
                },
                "sha256": {
                  "type": "string"
                }
              }
            },
            "description": "Somente o certificado combinado."
          },
          "label": {
            "type": "string",
            "description": "Seu rótulo, ou \"N files\" para um certificado combinado.",
            "nullable": true
          },
          "certified_at": {
            "type": "string",
            "format": "date-time"
          },
          "timestamps": {
            "type": "object",
            "properties": {
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending_confirmation",
                      "not_present"
                    ],
                    "description": "pending_confirmation: manifest.json.ots está no pacote e aguarda um bloco Bitcoin. not_present: não foi possível acessar os calendários OpenTimestamps, o pacote não tem manifest.json.ots e o manifest.json não o lista."
                  },
                  "block_height": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              },
              "eidas": {
                "$ref": "#/components/schemas/EidasTimestamp"
              }
            }
          },
          "package_url": {
            "type": "string",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "verify_url": {
            "type": "string",
            "description": "Link para o verificador público do ProofSnap.",
            "format": "uri"
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "PlainText": {
        "type": "string",
        "description": "Regra de texto simples para campos curtos de texto livre que são impressos em documentos destinados a tribunais ou em páginas públicas (case_reference, case_id, case_note, valores de evidence_pack.custodian, o case_reference de certify-hash, o custodiante do tarjamento, a declaração da exportação, nomes de escritórios e de casos). O valor é normalizado em NFC, os espaços em branco são condensados e aparados, e o comprimento é contado em caracteres (code points Unicode). Recusados: caracteres de controle (C0, C1), embeddings, overrides e isolates bidi (U+202A a U+202E, U+2066 a U+2069), marcas de direção (U+200E, U+200F, U+061C), caracteres de largura zero e invisíveis (U+200B, U+2060 a U+2064, U+FEFF, U+00AD, U+034F, U+180E, U+206A a U+206F, U+FFF9 a U+FFFB), os caracteres de preenchimento Hangul (U+115F, U+1160, U+3164, U+FFA0), caracteres de tag (U+E0000 a U+E007F), surrogates isolados e caracteres de uso privado. Letras de todos os sistemas de escrita são aceitas, incluindo sílabas Hangul, árabe, hebraico, devanágari e CJK, e U+200C / U+200D (ZWNJ, ZWJ) são permitidos porque a grafia correta em vários sistemas de escrita os exige."
      },
      "EidasTimestamp": {
        "type": "object",
        "description": "Resultado do eIDAS quando include_eidas foi solicitado com uma chave live; ausente quando não foi solicitado. confirmed: o pacote contém manifest.json.tsr (cobrado, a menos que a revogação seja desconhecida). not_obtained: nenhum token foi entregue e nada foi cobrado; o pacote contém apenas a ancoragem OpenTimestamps (reason token_refused: um token foi retornado e não passou na verificação na emissão; unavailable: não foi possível obter nenhum token; time_budget: a requisição não coube no tempo restante, então não foi feita, foi recusada pelo serviço de carimbo do tempo antes de qualquer requisição à autoridade ou foi interrompida, o que acontece na rota síncrona de tarjamento ou no fim de uma captura longa; account_limit: a conta ainda não tem nenhuma fatura paga e atingiu o seu limite de eIDAS de 20 carimbos por dia UTC ou 50 por mês UTC, contados em conjunto entre capturas, lotes, agendamentos, certificações, comparações, tarjamentos e selamentos de casos, então nenhuma requisição foi feita; contas com uma fatura paga não têm limite). Registros concluídos antes de 2026-10-01 informam apenas confirmed.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "time_budget",
              "account_limit"
            ],
            "description": "Apenas not_obtained."
          },
          "serial": {
            "type": "string",
            "nullable": true,
            "description": "Somente confirmed."
          },
          "issued_at": {
            "type": "string",
            "nullable": true,
            "description": "Somente confirmed."
          },
          "revocation": {
            "type": "string",
            "enum": [
              "unknown"
            ],
            "description": "Apenas confirmed, e apenas quando o status do certificado da TSA não pôde ser estabelecido na emissão (o token passou em todas as demais verificações). O token é entregue e não é cobrado."
          },
          "billed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Presente (false) junto com a revogação unknown: este token não foi cobrado."
          }
        }
      },
      "ExternalArchive": {
        "type": "object",
        "description": "O resultado do Internet Archive (Wayback Machine) de uma captura. O Internet Archive é um arquivo público de terceiros.",
        "required": [
          "requested"
        ],
        "properties": {
          "requested": {
            "type": "boolean",
            "description": "false quando nenhuma requisição ao Internet Archive foi feita (external_archive: false, uma certificação ou comparação, uma captura feita antes de o arquivamento existir, ou a requisição não coube no orçamento de tempo). Todos os demais campos ficam então ausentes."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Resultado atual. Para um job do Save Page Now ainda pendente no momento da selagem, o agendador lê o status do job cerca de 5 e 30 minutos após a requisição, e este passa a ser o resultado final (created, recent_existing, failed, rate_limited, ou unconfirmed quando nenhuma resposta chegou dentro da janela de verificação). created, recent_existing, created_after_request e existing trazem snapshot_url."
          },
          "status_at_sealing": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "O estado selado no pacote (metadata.json, forensic_log.json, chain_of_custody.json e evidence.pdf o registram). O pacote em si nunca muda; igual a status, a menos que um acompanhamento posterior tenha resolvido um job pendente."
          },
          "method": {
            "type": "string",
            "nullable": true,
            "enum": [
              "spn2_authenticated",
              "spn_anonymous",
              null
            ]
          },
          "requested_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "snapshot_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "A cópia pública no Wayback Machine, sempre https://web.archive.org/web/{snapshot_timestamp}/{url}."
          },
          "snapshot_timestamp": {
            "type": "string",
            "nullable": true,
            "description": "Timestamp do Wayback YYYYMMDDhhmmss (UTC)."
          },
          "snapshot_offset_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "Horário do snapshot menos o horário da captura, em segundos (negativo: antes da captura)."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Quando o acompanhamento do agendador registrou o resultado final; null quando o estado selado era o final."
          },
          "job_id": {
            "type": "string",
            "nullable": true,
            "description": "ID do job do Save Page Now (requisições autenticadas)."
          },
          "follow_up_pending": {
            "type": "boolean",
            "description": "true enquanto o agendador ainda precisa ler o status do job."
          },
          "skip_reason": {
            "type": "string",
            "nullable": true,
            "description": "Por que a URL não foi enviada, quando o status é skipped, skipped_sensitive ou skipped_query: credentials_in_url, sensitive_path, opaque_path_segment, sensitive_fragment, sensitive_parameter_name, opaque_parameter_value, query_string (ou o próprio status em registros mais antigos)."
          },
          "resolved": {
            "type": "object",
            "nullable": true,
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "recent_existing",
                  "failed",
                  "rate_limited",
                  "unresolved"
                ]
              },
              "job_status": {
                "type": "string",
                "nullable": true,
                "description": "success, error ou pending, conforme lido do job do Save Page Now; null quando não pôde ser lido."
              },
              "snapshot_url": {
                "type": "string",
                "nullable": true,
                "description": "https://web.archive.org/web/<14 digits>/<URL>, null quando ausente ou inválido."
              },
              "snapshot_timestamp": {
                "type": "string",
                "nullable": true
              },
              "checked_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "no_request_time",
                  "capture_deleted",
                  "capture_failed",
                  null
                ],
                "description": "Por que não há resposta do job: no_request_time (o registro não tem horário de requisição a partir do qual agendar as leituras), capture_deleted ou capture_failed (a captura foi enviada para a lixeira ou falhou enquanto o job estava pendente, então não foi acompanhada; uma captura restaurada da lixeira em até 30 minutos após a requisição volta a ser acompanhada). null nos demais casos."
              }
            },
            "description": "O registro de acompanhamento (histórico), como uma projeção fixa: snapshot_url somente quando é uma URL de snapshot Wayback válida de snapshot_timestamp; reason somente quando o acompanhamento terminou sem resposta do job."
          }
        }
      },
      "CaptureStorage": {
        "type": "object",
        "description": "Retenção, a cópia duplicada e (capturas automáticas) o histórico de versões.",
        "properties": {
          "retention": {
            "type": "object",
            "properties": {
              "years": {
                "type": "integer",
                "example": 10
              },
              "counted_from": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "O mais tardio entre created_at e o último uso conhecido: a última verificação que encontrou a página inalterada, ou o momento em que uma captura mais recente do mesmo agendamento substituiu esta."
              },
              "earliest_deletion_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "counted_from mais years, aproximado: a exclusão é executada em uma varredura posterior do agendador, e verificações posteriores podem alterá-la. null sob retenção legal."
              },
              "legal_hold": {
                "type": "boolean"
              }
            }
          },
          "duplicate_copy": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "stored",
                  "not_copied"
                ],
                "description": "stored: uma segunda cópia do pacote (e da sua prova OTS atualizada) é mantida em um bucket separado em outra região da UE e excluída junto com o original. pending: em cópia (minutos). none: pacotes de teste e pacotes anteriores à existência da cópia duplicada, até que sejam copiados."
              },
              "copied_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "version": {
            "type": "object",
            "description": "Somente capturas automáticas.",
            "properties": {
              "superseded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "superseded_by": {
                "type": "string",
                "nullable": true
              },
              "unchanged_first_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Primeira verificação (on_change) que encontrou o texto da página inalterado desde esta captura."
              },
              "unchanged_last_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "unchanged_checks": {
                "type": "integer",
                "description": "Verificações sem alteração registradas (no máximo uma por hora)."
              }
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "capture.completed",
          "capture.failed",
          "certify.completed",
          "certify.failed",
          "redaction.completed",
          "redaction.failed",
          "monitor.change_detected",
          "capture.anchor_confirmed",
          "schedule.paused"
        ],
        "description": "Um evento do catálogo (tag Webhooks)."
      },
      "WebhookSubscriptionFilters": {
        "type": "object",
        "description": "No máximo um entre host e schedule_id. A assinatura passa a receber apenas os eventos das linhas com esse host (hostname em minúsculas da URL capturada) ou desse agendamento. `{}` para nenhum.",
        "properties": {
          "host": {
            "type": "string",
            "example": "www.example.com"
          },
          "schedule_id": {
            "type": "string",
            "example": "sch_a1B2c3D4e5F6g7H8"
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "Endpoint https para o qual os eventos são enviados via POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "O ambiente da credencial que a criou. Uma assinatura live recebe apenas os eventos de linhas live, uma assinatura de teste apenas os de linhas de teste."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: criada com uma chave de API; revogar essa chave desativa a assinatura (disabled_reason key_revoked)."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Entregas com falha desde o último sucesso."
          },
          "last_success_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_failure_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "Classe de erro da última entrega com falha (timeout, connection_failed, blocked_address, redirect_not_followed, http_<status>)."
          },
          "disabled_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gone",
              "failing",
              "key_revoked",
              null
            ],
            "description": "gone: o endpoint respondeu com 410. failing: todas as entregas falharam por 5 dias sem nenhum sucesso. key_revoked: a chave de API que a criou foi revogada. PATCH status active a reativa."
          },
          "disabled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "consecutive_failures",
          "last_success_at",
          "last_failure_at",
          "last_error",
          "disabled_reason",
          "disabled_at"
        ]
      },
      "WebhookSubscriptionCreated": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "Endpoint https para o qual os eventos são enviados via POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "O ambiente da credencial que a criou. Uma assinatura live recebe apenas os eventos de linhas live, uma assinatura de teste apenas os de linhas de teste."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: criada com uma chave de API; revogar essa chave desativa a assinatura (disabled_reason key_revoked)."
          },
          "secret": {
            "type": "string",
            "description": "whsec_ seguido de base64 padrão. Exibido UMA VEZ: toda entrega para esta assinatura é assinada com ele (Standard Webhooks). Guarde-o agora; ele não pode ser lido de novo. Uma repetição com Idempotency-Key dentro de 24 horas retorna o mesmo corpo, incluindo o segredo."
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "secret"
        ]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_a1B2c3D4e5F6g7H8"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "data": {
            "type": "object",
            "description": "O corpo do evento como as assinaturas o recebem, sem subscription_id e sem links pré-assinados (package_url, package_url_expires_at, upgraded_proof_url, compare.package_url)."
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "env",
          "data"
        ]
      },
      "PrivateNote": {
        "type": "string",
        "nullable": true,
        "maxLength": 200,
        "description": "Sua nota privada sobre a captura (por exemplo \"captura mobile\"), no máximo 200 caracteres, texto simples (a regra PlainText). Ela é armazenada apenas no registro da captura: NÃO faz parte do pacote de provas selado (nem no ZIP, no PDF, no manifesto ou em qualquer hash) e não tem valor probatório, então pode ser alterada ou apagada depois com PATCH /v1/captures/{capture_id} sem afetar a prova. Somente quem fez a captura pode lê-la. Omita-a, ou envie null ou uma string vazia, para não ter nota.",
        "example": "mobile capture"
      },
      "CaptureEvent": {
        "type": "object",
        "description": "Um evento da cadeia de eventos de captura da conta. hash = SHA-256 (hex) do JSON canônico dos campos com hash (v, chain, seq, type, capture_id, manifest_sha256, actor, access, detail, at, prev_hash; chaves ordenadas, sem espaços em branco, valores ausentes como null). prev_hash é o hash do evento anterior, 64 zeros para seq 1.",
        "properties": {
          "v": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "chain": {
            "type": "string",
            "description": "A conta: o uid para capturas pessoais, org_<org_id> para as capturas de um escritório."
          },
          "seq": {
            "type": "integer",
            "minimum": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "trash",
              "restore",
              "erase",
              "purge",
              "retention_deletion",
              "account_deletion",
              "company_deletion",
              "legal_hold_on",
              "legal_hold_off",
              "s3_legal_hold",
              "import"
            ]
          },
          "capture_id": {
            "type": "string"
          },
          "manifest_sha256": {
            "type": "string",
            "nullable": true,
            "description": "SHA-256 do manifest.json da captura, lido antes de qualquer exclusão."
          },
          "actor": {
            "type": "string",
            "description": "O uid da conta que executou a ação, ou system para as varreduras do servidor."
          },
          "access": {
            "type": "string",
            "description": "author, company_owner, matter_owner ou system."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "legal_hold_on / legal_hold_off: a origem da retenção (manual, bulk, matter:<id>, derived:<capture_id>) ou, para uma liberação feita pelo sistema (actor system), seu código de motivo subscription_ended; s3_legal_hold: o resultado no S3 (on, off, failed, unsupported, none); import: o tipo do pacote importado. Sem texto livre."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando o evento foi gravado na cadeia. Nunca antes do at do evento anterior."
          },
          "prev_hash": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando a própria ação foi confirmada (a transação da mudança de estado). Não entra no hash. Normalmente igual a at, com diferença de milissegundos; posterior apenas quando o servidor tentou novamente o acréscimo à cadeia (a ação é registrada na mesma transação que a mudança, então nenhuma se perde)."
          }
        }
      },
      "CaptureEventAnchor": {
        "type": "object",
        "description": "Uma ancoragem OpenTimestamps diária da cadeia: head_hash é o hash do evento to_seq, que se vincula a todos os eventos anteriores.",
        "properties": {
          "anchor_id": {
            "type": "string"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "from_seq": {
            "type": "integer"
          },
          "to_seq": {
            "type": "integer"
          },
          "head_hash": {
            "type": "string"
          },
          "ots_proof_base64": {
            "type": "string",
            "nullable": true,
            "description": "A prova OpenTimestamps (arquivo .ots, base64) sobre head_hash."
          },
          "ots_status": {
            "type": "string",
            "enum": [
              "pending",
              "bitcoin",
              "unconfirmed"
            ],
            "description": "pending: ots_proof_base64 contém o compromisso dos calendários (ots verify consulta os calendários); bitcoin: foi substituído pela prova atualizada para o bloco do Bitcoin (bitcoin_block_height); unconfirmed: nenhuma prova Bitcoin foi retornada em 14 dias, a prova armazenada ainda pode ser verificada pelos calendários."
          },
          "bitcoin_block_height": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportSummary": {
        "type": "object",
        "description": "Os três níveis de um pacote verificado.",
        "properties": {
          "kind_label_key": {
            "type": "string",
            "description": "Chave do rótulo do tipo de pacote, import_kind_<kind>."
          },
          "integrity": {
            "type": "string",
            "enum": [
              "verified",
              "failed"
            ]
          },
          "time_anchor": {
            "type": "string",
            "enum": [
              "eidas_qualified",
              "eidas_qualified_revocation_unknown",
              "ots_bitcoin_attested_unconfirmed",
              "ots_pending",
              "none"
            ]
          },
          "provenance": {
            "type": "string",
            "nullable": true,
            "enum": [
              "issued_by_proofsnap",
              "unverified",
              "registered_by_extension_account",
              "not_registered",
              "unknown",
              null
            ]
          },
          "registered_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando o registro da extensão registrou o pacote (provenance registered_by_extension_account)."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando o registro de emissão da API registrou a emissão (provenance issued_by_proofsnap)."
          }
        }
      },
      "Import": {
        "type": "object",
        "properties": {
          "import_id": {
            "type": "string",
            "example": "imp_a1B2c3D4e5F6g7H8i9J0"
          },
          "status": {
            "type": "string",
            "enum": [
              "uploading",
              "verifying",
              "accepted",
              "rejected",
              "duplicate",
              "expired"
            ]
          },
          "filename": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "upload_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Somente uploading: conclua antes deste horário (24 horas após a criação)."
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando a importação atingiu o seu status final."
          },
          "capture_id": {
            "type": "string",
            "description": "Apenas accepted: a nova captura (o mesmo id da importação)."
          },
          "duplicate_of": {
            "type": "string",
            "description": "Somente duplicate: a sua captura que já contém este manifest."
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ],
            "description": "O tipo de pacote, depois de detectado."
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "rejected e expired: códigos de motivo, por exemplo file_mismatch, file_missing, unlisted_file, bad_signature, no_hashed_files, unknown_schema, zip_too_large, test_package, ots_digest_mismatch, eidas_token_failed, forensic_chain_broken, size_mismatch, upload_not_completed, verification_incomplete. Somente códigos, nunca nomes de arquivo."
          },
          "zip_sha256": {
            "type": "string",
            "description": "SHA-256 do ZIP enviado, depois de calculado."
          },
          "manifest_sha256": {
            "type": "string",
            "description": "SHA-256 do manifest.json do pacote (accepted e duplicate)."
          },
          "matter_id": {
            "type": "string",
            "description": "O caso indicado quando a importação foi criada."
          },
          "matter_assignment": {
            "type": "string",
            "enum": [
              "assigned",
              "skipped"
            ],
            "description": "accepted com matter_id: se a captura foi adicionada ao caso (ignorada quando o caso já estava selado, arquivado ou não estava mais aberto para você; a importação continua accepted)."
          },
          "upload": {
            "type": "object",
            "description": "Apenas com ?upload=true enquanto o status é uploading.",
            "properties": {
              "upload_id": {
                "type": "string"
              },
              "part_size": {
                "type": "integer"
              },
              "parts": {
                "type": "array",
                "description": "URLs PUT pré-assinadas das partes ainda não recebidas; envie via PUT exatamente size_bytes.",
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "received": {
                "type": "array",
                "nullable": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "etag": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "completed": {
                "type": "boolean",
                "description": "O upload já foi concluído: envie complete novamente."
              }
            }
          }
        }
      },
      "ImportVerification": {
        "type": "object",
        "description": "O relatório de verificação registrado na importação (sem conteúdo de arquivos, sem detalhes em texto livre).",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "format": {
            "type": "string"
          },
          "kind_label_key": {
            "type": "string"
          },
          "evidence_id": {
            "type": "string",
            "description": "O id que o pacote traz (ps_..., FC-..., cap_..., cert_..., cmp_..., red_...)."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "anchor_sha256": {
            "type": "string",
            "description": "O hash coberto pelos carimbos do tempo: o do manifesto ou, em uma certificação de arquivo único, o do arquivo certificado."
          },
          "anchor_source": {
            "type": "string",
            "enum": [
              "manifest",
              "certified_file"
            ]
          },
          "checks": {
            "type": "object",
            "description": "structure, signature, files (listed, verified, presence_only, unhashed, os_junk_ignored), custody, forensic_chain, ots, eidas, test_mode.",
            "additionalProperties": true
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CaptureImport": {
        "type": "object",
        "description": "Somente em uma captura com source \"import\".",
        "properties": {
          "import_id": {
            "type": "string"
          },
          "package_kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "kind_label_key": {
            "type": "string"
          },
          "package_format": {
            "type": "string"
          },
          "original_evidence_id": {
            "type": "string"
          },
          "original_filename": {
            "type": "string",
            "nullable": true
          },
          "imported_at": {
            "type": "string",
            "format": "date-time"
          },
          "captured_at_basis": {
            "type": "string",
            "enum": [
              "package_claim"
            ],
            "description": "captured_at de um pacote importado é o horário que o pacote declara, nunca um horário observado pelo ProofSnap."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "levels": {
            "type": "object",
            "properties": {
              "integrity": {
                "type": "string"
              },
              "time_anchor": {
                "type": "string"
              },
              "provenance": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "verification": {
            "$ref": "#/components/schemas/ImportVerification"
          },
          "provenance": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "api_ledger",
                  "extension_registry",
                  null
                ]
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "registered_at": {
                "type": "string",
                "nullable": true
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      },
      "Rule902DisabledWarnings": {
        "type": "array",
        "description": "Presente somente quando a requisição pediu o modelo de certificação (include_rule_902: true, ou, para uma captura, um evidence_pack.custodian não vazio) enquanto o modelo está desativado. A requisição foi aceita e a flag ignorada: nenhum rule_902_certification.pdf é gerado.",
        "items": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "enum": [
                "RULE902_DISABLED"
              ]
            },
            "message": {
              "type": "string",
              "example": "The FRE 902 certification template (rule_902_certification.pdf) is currently disabled. include_rule_902 was accepted and ignored: the package does not contain the template."
            }
          },
          "required": [
            "code",
            "message"
          ]
        }
      }
    }
  }
}
