Início rápido da API

Um endpoint, duas formas de requisição, um relatório JSON. Sem SDK — e não pretendemos oferecer um.

A Tamperlens tem uma superfície deliberadamente pequena: POST /api/v1/inspect recebe um PDF e devolve sinais estruturais de risco de fraude em JSON. Todo o resto desta página é autenticação, limites e tratamento de erro. A especificação legível por máquina, com console de teste, está em /docs (em inglês).

1. Pegue uma chave

Crie uma conta — e-mail e senha, sem cartão. O cadastro emite uma chave do plano Free na hora, com cota de 25 documentos por mês.

ou pelo terminal

curl -s -c /tmp/tl.jar https://tamperlens.com/api/v1/auth/signup \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"uma-senha-longa-o-suficiente"}'

# -> {"user":{…},"apiKey":{"plaintext":"tl_…","plan":"free","quota":25}}

A chave é exibida exatamente uma vez. Guardamos apenas um hash SHA-256, então ela não pode ser exibida de novo — o painel mostra um preview não reversível que não revela nada sobre o segredo. Se você perdê-la, rotacione a chave no painel ou com POST /api/v1/keys/rotate; a rotação revoga a chave antiga e emite uma nova.

Você também pode chamar o endpoint sem chave nenhuma. Chamadas anônimas funcionam e devolvem o relatório completo — elas são limitadas por IP do cliente em vez de medidas contra uma cota. É esse o modo que o verificador web gratuito usa. Serve para experimentar e é errado para produção.

2. Autenticação

Um bearer token no header Authorization. As chaves têm o prefixo tl_.

Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Três comportamentos que vale conhecer, porque são deliberados:

  • Sem header → anônimo. Permitido, com rate limit por IP.
  • Um header que não resolve401 na hora. Uma chave apresentada e inválida nunca degrada silenciosamente para anônimo, porque é essa degradação silenciosa que transforma uma chave rotacionada em produção num mistério em vez de num erro.
  • Uma chave válida → medida contra a cota mensal daquela chave, e totalmente isenta do rate limit por IP dos anônimos.

O endpoint de análise autentica só por header, nunca por cookie, então não há nada a configurar em CORS com credenciais e nada com cara de CSRF para se preocupar. Os endpoints de conta e cobrança são o oposto — cookie de sessão, corpo JSON obrigatório — e estão documentados em /docs.

3. Fazendo uma requisição

POST /api/v1/inspect aceita multipart/form-data com um campo chamado file, ou um corpo bruto. As duas formas são equivalentes; escolha a que for mais barata no seu cliente HTTP. O limite é 10 MB.

Três mídias, um endpoint e um formato de relatório. PDFs; documentos do Word, Excel e PowerPoint (.docx, .xlsx, .pptx e os equivalentes com macro); e imagens JPEG, PNG e WebP. HEIC, TIFF, GIF e BMP são reconhecidos e reportados, mas seus contêineres não são percorridos — e o relatório diz isso num sinal, porque um resultado vazio nunca pode ser confundido com um resultado limpo.

O formato é decidido farejando os bytes, nunca pelo cabeçalho Content-Type nem pelo nome do arquivo. Os relatórios trazem um discriminador mediaType"pdf", "office" ou "image" — mais um bloco aditivo office ou image; document continua preenchido nos três casos, então uma integração existente continua funcionando.

multipart/form-data

curl -s https://tamperlens.com/api/v1/inspect \
  -H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F [email protected]

corpo bruto application/pdf

curl -s https://tamperlens.com/api/v1/inspect \
  -H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/pdf" \
  --data-binary @extrato.pdf

Node — corpo bruto, sem encoder de multipart

const pdf = await readFile("extrato.pdf");

const res = await fetch("https://tamperlens.com/api/v1/inspect", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TAMPERLENS_KEY}`,
    "Content-Type": "application/pdf",
  },
  body: pdf,
});

if (!res.ok) throw new Error(`inspect falhou: ${res.status}`);
const report = await res.json();

if (report.summary.riskBand !== "low") {
  await enviarParaAnaliseManual(applicationId, report.signals);
}

Python

import os, requests

with open("extrato.pdf", "rb") as fh:
    res = requests.post(
        "https://tamperlens.com/api/v1/inspect",
        headers={
            "Authorization": f"Bearer {os.environ['TAMPERLENS_KEY']}",
            "Content-Type": "application/pdf",
        },
        data=fh.read(),
        timeout=30,
    )

res.raise_for_status()
report = res.json()
print(report["summary"]["riskBand"], [s["id"] for s in report["signals"]])

Verificando um arquivo contra o original: /compare

Quando você tem o original em mãos — você gerou o documento, ou guardou a cópia que o emissor enviou — POST /api/v1/compare responde a uma pergunta mais forte do que a análise isolada consegue: este arquivo é aquele original, sem alterações? Envie dois arquivos e receba os fatos em três camadas, da mais forte para a mais fraca.

Comparar um arquivo reenviado com o que está em registro

curl -s https://tamperlens.com/api/v1/compare \
  -H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F [email protected] \
  -F [email protected]
  • relationship: "identical" — os arquivos têm o mesmo hash. A única resposta que é uma prova, e não um sinal.
  • "candidate-extends-original" — o candidato contém o original byte a byte, mais atualizações incrementais anexadas. O relatório classifica o que as revisões anexadas tocam; ancestry.contentTouched é o fato principal — o conteúdo da página foi substituído depois que o original estava completo. "candidate-truncates-original" é o inverso: o candidato é uma revisão anterior do original.
  • "rewritten" — nenhum contém o outro, e a comparação recua com honestidade para a estrutura interpretada: metadados campo a campo, o par /ID, fontes, número e geometria das páginas. Nenhuma página é renderizada, então nenhum resultado afirma que a aparência visual é idêntica.

O /compare exige uma chave (não há acesso anônimo) e interpreta dois documentos, então é medido como 2 documentos na cota mensal. Apenas pares de PDF. O schema completo está em /docs, e o guia explica o que cada relação prova — e o que não prova.

Lendo os metadados de um arquivo: /metadata

O POST /api/v1/metadata responde a uma pergunta diferente da inspeção: não isto foi editado?, mas o que este arquivo diz sobre si mesmo? Todas as chaves, agrupadas pelo contêiner de onde vieram, com a forma armazenada ao lado da decodificada.

Liste tudo o que um documento carrega

curl -s https://tamperlens.com/api/v1/metadata \
  -F [email protected]
  • PDF — o dicionário Info completo, não só os cinco campos que as famílias de sinais leem. Author, Subject, Keywords e qualquer chave personalizada inventada por um sistema aparecem todas, além de todas as propriedades XMP e dos fatos estruturais (páginas, revisões, criptografia, assinaturas, fontes).
  • Imagens — EXIF separado por diretório, para que os fatos da miniatura continuem distinguíveis dos da fotografia; coordenadas de GPS decodificadas; blocos de texto de PNG; XMP. Contêineres presentes mas não enumerados (IPTC, ICC, MakerNote, C2PA) são nomeados em vez de omitidos em silêncio.
  • Office — propriedades core, de aplicação e personalizadas, e o EXIF de cada imagem embutida no pacote. Um .docx é um ZIP: uma foto colada nele mantém as coordenadas que a câmera escreveu, e isso costuma ser o mais surpreendente do relatório.

O campo personal reúne as entradas que identificam uma pessoa, um aparelho ou um lugar — nomes, números de série, programas, localizações — como cópia, para que a lista agrupada continue completa. Não há sinais, nota de risco nem veredicto; isso é trabalho do /inspect.

Somente leitura, por construção. A resposta é JSON e nada além de JSON. A Tamperlens não tem nenhum endpoint que devolva um arquivo modificado — nem cópia limpa, nem download sem metadados — porque produzir um significaria escrever um documento, e todas as promessas da página de segurança dependem de nunca fazermos isso. Se você precisa de um arquivo limpo, limpe localmente; nós dizemos exatamente o que remover.

Um arquivo, uma leitura, medido como 1 documento. Funciona anonimamente como o verificador gratuito, então a chave é opcional. A versão no navegador está em /pt/metadados.

Health check, sem autenticação: GET /api/v1/health.

A partir de um agente (MCP)

Se quem chama é um agente de LLM e não o seu próprio código, existe um servidor MCP que expõe os mesmos endpoints como três ferramentas — inspect_document, check_redaction e compare_documents. Ele é um invólucro fino sobre a API HTTP desta página; não há um segundo motor atrás dele.

{
  "mcpServers": {
    "tamperlens": {
      "command": "node",
      "args": ["/caminho/absoluto/para/tamperlens/dist/mcp/index.js"],
      "env": { "TAMPERLENS_API_KEY": "tl_..." }
    }
  }
}

As ferramentas recebem um caminho absoluto de arquivo, nunca base64 embutido: os bytes precisam chegar à Tamperlens, não ao modelo, e passar um documento pelo contexto de um agente custaria megabytes à toa.

Ainda não está no npm. O servidor roda a partir de um build local — clone o repositório, rode npm run build e aponte o seu host MCP para dist/mcp/index.js, como acima.

4. Baselines de emissor

As onze famílias de sinais acima respondem a uma pergunta: este arquivo foi alterado depois de gerado? Elas nada dizem sobre um documento fabricado do zero — gerado uma única vez a partir de um modelo e nunca mais tocado. Um arquivo assim é estruturalmente impecável, então nada dispara.

Um baseline de emissor responde à outra pergunta: este arquivo se parece com o que a instituição citada realmente emite? Toda instituição gera seus PDFs com uma cadeia de ferramentas fixa, e essa cadeia deixa uma impressão digital estrutural consistente — o mesmo produtor, a mesma versão de PDF, o mesmo conjunto de fontes incorporadas, a mesma geometria de página, o mesmo estilo de tabela de referências cruzadas, uma única revisão. Um extrato falso gerado com wkhtmltopdf é internamente coerente, mas errado para o banco cujo nome está nele — e isso é visível sem ler um único número da página.

O que um baseline guarda

Um perfil é apenas estrutura derivada: ele registra que os documentos de um emissor trazem o produtor X e a versão de PDF 1.4. Ele nunca contém os documentos, o texto deles, números de conta, nomes ou saldos — e ocupa algumas centenas de bytes. Os documentos de treino são lidos em memória e descartados exatamente como em uma análise, de modo que “documentos enviados nunca são armazenados” continua literalmente verdadeiro com este recurso ligado.

Informando o emissor em uma análise

Opcional e aditivo. Envie o slug do emissor como um campo multipart issuer (antes da parte do arquivo) ou como o cabeçalho X-Tamperlens-Issuer em um corpo cru. Os slugs seguem ^[a-z0-9][a-z0-9-]{0,63}$.

multipart

curl -s https://tamperlens.com/api/v1/inspect \
  -H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -F issuer=chase \
  -F [email protected]

corpo cru

curl -s https://tamperlens.com/api/v1/inspect \
  -H "Authorization: Bearer tl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/pdf" \
  -H "X-Tamperlens-Issuer: chase" \
  --data-binary @extrato.pdf

A resposta é o relatório de sempre mais um bloco no nível raiz. Uma divergência confiável também levanta um sinal issuer-mismatch em signals.

{
  "issuerBaseline": {
    "requested": "chase",
    "resolved": true,
    "source": "customer",
    "sampleCount": 18,
    "matchingActive": true,
    "divergence": 0.42,
    "mismatchedDimensions": ["producers", "fontSets"]
  }
}

Um slug desconhecido não é erro. Você recebe o relatório normal e {"requested": "chase", "resolved": false, "reason": "no_profile"}, para que “combinou” e “não havia com o que comparar” nunca se confundam — uma distinção que a ausência de um sinal não consegue expressar. Um slug malformado é 422 invalid_issuer, porque só pode ser um bug de quem chama. Omita issuer e a resposta é byte a byte a mesma de antes deste recurso existir: a chave nem aparece.

Treinando os seus

Os baselines que você treina com seus próprios documentos comprovadamente legítimos são privados da sua conta e sempre vencem os que já vêm incluídos. Um pequeno conjunto de perfis públicos de bancos acompanha o produto como alternativa. Perfis nunca são combinados entre contas.

curl -s https://tamperlens.com/api/v1/baselines \
  -H "Cookie: tl_session=$SESSION" \
  -F issuer=chase \
  -F label="JPMorgan Chase" \
  -F [email protected] -F [email protected] -F [email protected] \
  -F [email protected] -F [email protected] -F [email protected]
  • GET /api/v1/baselines lista os seus perfis e os incluídos, com builtIn separando uns dos outros e um índice de consistency por dimensão.
  • DELETE /api/v1/baselines/:issuer remove um dos seus. Um perfil incluído é compartilhado e responde 404.
  • Cinco documentos legítimos é o piso para que um perfil consiga apontar uma divergência, e uma dimensão em que os seus documentos discordam simplesmente não gera expectativa. Treinar o mesmo emissor de novo funde com o perfil existente, então um baseline pode crescer em lotes.
  • Treinar não consome cota de análise. O limite é outro: 25 documentos por requisição, 25 perfis por conta, 5 treinos por hora.

Estes três endpoints autenticam por sessão do painel, e não por chave de API — o jeito mais simples de usá-los é pelo painel.

5. Sua política de veredicto

A Tamperlens não decide se um documento é fraudulento, e a seção 11 explica por que isso é uma posição, e não uma esquiva. Mas você pode ter decidido — e, antes disto existir, precisava reimplementar essa decisão do seu lado: cada integrador escrevendo o mesmo if (riskScore >= 70) de um jeito ligeiramente diferente, nada disso visível para nós quando você perguntava por que um documento tirou a nota que tirou.

Então mande a regra junto com a requisição. As mesmas duas grafias do emissor: um campo policy no multipart, antes da parte do arquivo, ou um cabeçalho X-Tamperlens-Policy. Os dois carregam um objeto JSON.

curl -X POST https://tamperlens.com/api/v1/inspect \
  -H "Authorization: Bearer $TL_KEY" \
  -H 'X-Tamperlens-Policy: {"review":30,"reject":70,"rejectOn":["redaction-exposure"]}' \
  -H "Content-Type: application/pdf" \
  --data-binary @extrato.pdf

A resposta ganha um bloco, e nada mais muda:

"policy": {
  "verdict": "reject",          // accept | review | reject
  "riskScore": 45,              // a nota do próprio relatório, nunca recalculada
  "thresholds": { "review": 30, "reject": 70 },
  "reason": "signal",           // "score" quando um corte foi cruzado
  "triggeredBy": ["redaction-exposure"]
}
Campo Padrão O que faz
review 30 Nota a partir da qual o veredicto é review
reject 70 Nota a partir da qual o veredicto é reject
rejectOn [] Famílias de sinais que forçam reject, qualquer que seja a nota
reviewOn [] Famílias de sinais que forçam pelo menos review

Os cortes padrão são as próprias faixas do motor, então uma política sem configuração concorda com summary.riskBand em vez de discordar dele em silêncio. Uma família nomeada vence um corte, e rejectOn vence reviewOn — quem nomeia uma família está dizendo que ela importa independentemente do que a aritmética deu, e um corte capaz de vetar isso tornaria a regra apenas consultiva. Só sinais acima da severidade info podem disparar uma regra.

  • Ids de sinal desconhecidos são aceitos e simplesmente nunca casam. Renomear uma família não pode derrubar o seu pipeline.
  • Uma política malformada é 422 invalid_policy, nunca um retorno silencioso aos nossos padrões — um erro de digitação que deixasse você acreditando que uma regra está valendo quando não está é a única falha que isto não pode ter.
  • Omita e nada muda. A chave policy fica ausente da resposta, não nula, e o relatório é idêntico byte a byte ao que era antes deste recurso existir.

6. O relatório, anotado

Uma resposta 200 é um InspectionReport. O formato é um contrato estável; novas famílias de sinais são aditivas. Os campos de texto (title, detail, disclaimer) são gerados pelo motor em inglês — o verificador web traduz os títulos por família, mas a API devolve o texto original.

{
  // Identificador da chamada. O ÚNICO campo não determinístico da resposta.
  "id": "insp_2f1e9c04-7b3a-4d51-9f0e-6c2a18d4bb71",

  // Se isto mudar, os pesos do score podem ter mudado. Fixe nos seus testes.
  "engineVersion": "1.2.0",

  "summary": {
    "riskScore": 95,          // 0-100, agregação ponderada — não é probabilidade
    "riskBand": "high",       // "low" <30 · "elevated" 30-69 · "high" >=70
    "signalCount": 2,         // sinais com severidade acima de "info"
    "revisions": 1            // 1 = nunca sofreu atualização incremental
  },

  // Mais grave primeiro, depois em ordem alfabética por id. No máximo um por família.
  "signals": [
    {
      "id": "producer-fingerprint",     // uma das 10 ids estáveis de família
      "severity": "high",               // "info" | "low" | "medium" | "high"
      "title": "Consumer editing tool in the production chain (iLovePDF)",
      "detail": "The document's metadata names iLovePDF in its production chain. …",
      "evidence": {                     // formato varia por família; sempre legível por máquina
        "tools": ["iLovePDF"],
        "producer": "iLovePDF",
        "creator": "iText 7.2.5",
        "revisions": 1,
        "fullPageImagePages": [1]       // corroboração: é por isso que é "high"
      }
    },
    {
      "id": "id-inconsistency",
      "severity": "medium",
      "title": "Trailer /ID marks the file as changed since creation",
      "detail": "The trailer /ID array holds two different identifiers. …",
      "evidence": {
        "idOriginal": "8f2c...a91b",
        "idCurrent": "41de...77c0",
        "trailerCount": 1
      }
    }
  ],

  // Fatos, não achados. Qualquer campo de texto pode vir null.
  "document": {
    "pages": 2,
    "producer": "iLovePDF",
    "creator": "iText 7.2.5",
    "creationDate": "2026-01-04T10:02:00.000Z",   // ISO-8601, ou null
    "modDate": "2026-01-06T18:41:00.000Z",
    "encrypted": false,
    "signed": false,
    "revisions": 1,     // uma revisão, mas /ID divergente: reescrito por inteiro
    "sizeBytes": 184320
  },

  "disclaimer": "Tamperlens reports risk signals, not authenticity verdicts. …"
}

Campos em que você realmente deve ramificar

  • summary.riskBand — a decisão grosseira de roteamento. O contrato das faixas é estreito de propósito: high é reservado para relatórios com pelo menos um sinal de severidade alta, então um acúmulo de achados fracos é limitado em 69 e nunca chega lá.
  • signals[].id e severity — os ids de família são strings estáveis. Ramifique nelas quando quiser peso por sinal em vez de um único limiar, e você vai querer: se o seu fluxo envolve legitimamente clientes assinando documentos, você quer incremental-updates com peso menor e signature-coverage com peso maior.
  • signals[].evidence — guarde. É pequeno, não contém conteúdo do documento e é exatamente o que um analista humano precisa. Trate o formato como específico por família e aditivo; não assuma que as chaves estão presentes.
  • document.encrypted — quando true, cinco famílias de sinais dependentes de conteúdo não rodaram, e o relatório diz isso por meio de um sinal structure-warnings com evidence.code: "encrypted-content-not-analysed". A ausência de um sinal de conteúdo nesse relatório não significa nada. Detalhes.

Cada família, suas chaves de evidência, suas causas legítimas e as regras exatas de severidade: o guia de campo (em inglês).

7. Erros

Todos os erros são JSON com uma string error. Nada sobre o documento vaza no corpo de um erro.

Status Corpo Quando
400 {"error":"no_file"} requisição multipart sem a parte file
401 {"error":"invalid_api_key"} uma chave foi apresentada e não resolveu
413 {"error":"file_too_large","maxMb":10} corpo ou arquivo multipart acima do limite
422 {"error":"not_a_pdf"} os bytes não são um formato que a Tamperlens leia. O código é anterior ao suporte a Office e imagens e foi mantido para as integrações que dependem dele
422 {"error":"analysis_timeout","timeoutMs":15000} o documento esgotou o orçamento de CPU e a worker dele foi morta. Não faz sentido repetir — o problema é a entrada
429 {"error":"quota_exceeded","quota":25,"used":25} chamador com chave acima da cota mensal. A chamada recusada não é cobrada
429 corpo do rate limit chamador anônimo acima do limite por hora por IP
503 {"error":"busy","retryAfterSeconds":5} todas as workers ocupadas e a fila cheia; Retry-After: 5 é enviado. Contrapressão honesta — repita
500 {"error":"internal_error"} culpa nossa. Genérico de propósito; o interno nunca vaza

Repita o 503 com backoff. Não repita o 422. No 429 quota_exceeded, enfileire em vez de descartar — o contador zera todo mês.

Note o que não é erro: um arquivo que o parser não consegue entender. Entrada ilegível ou hostil degrada para um 200 carregando um sinal structure-warnings, em vez de lançar exceção. Um PDF quebrado é um achado, não uma falha.

8. Rate limits e cotas

  • Anônimo: 10 documentos por hora por IP do cliente. Sem cota, sem chave, relatório completo. Pensado para o verificador web e para experimentar a API.
  • Com chave: sem limite por hora; uma cota mensal de documentos por chave, definida pelo plano. O uso é contado por análise bem-sucedida; uma chamada recusada por cota não conta.
  • Concorrência: as análises rodam num pool de worker threads com fila limitada e prazo rígido de 15 segundos por documento. Quando a fila enche, a API descarta carga com 503 busy e um header Retry-After, em vez de deixar a latência crescer sem limite.
  • Tamanho: 10 MB por documento.

Endpoints de sessão do painel. GET /api/v1/me e GET /api/v1/usage autenticam com o cookie de sessão do painel, não com um bearer token — uma requisição que leve apenas Authorization: Bearer … recebe 401 not_authenticated. Eles servem o painel; a superfície de API com chave é /inspect, /compare e /baselines.

Uso do mês corrente: GET /api/v1/me. Detalhamento por dia do mês atual: GET /api/v1/usage. Os dois também aparecem no painel.

9. Preços

Plano Preço Documentos / mês Observações
Free US$ 0 25 Relatório completo, sem marca d'água, sem cartão. Mais o verificador web.
Solo US$ 15/mês 200 Acesso completo à API. Cartão, autoatendimento.
Starter US$ 49/mês 1.000 Inclui baselines de emissor privados. Sem reunião, sem contrato.
Growth US$ 199/mês 10.000 Inclui suporte prioritário por e-mail.

Todo plano recebe o mesmo motor, os mesmos sinais e a mesma latência. Não existe um tier enterprise que libere detecção melhor, porque não há detecção melhor para liberar. A única divisão de recurso são os baselines de emissor privados (Starter e acima) — o que se restringe é um repositório de dados que você treina, não o motor; os baselines públicos embutidos funcionam em todos os planos.

Cartões emitidos no Brasil são cobrados em reais (R$) no checkout; os valores em dólar valem para cartões internacionais.

Upgrades e downgrades são autoatendimento pelo painel (Stripe Checkout e o portal de cobrança da Stripe). Cancelar devolve a chave ao plano Free e à cota de 25 documentos, em vez de desativá-la. Acima de 10.000 documentos por mês, mande um e-mail — a resposta honesta é que preferimos conversar sobre o seu volume do que chutar um preço para ele.

25 documentos por mês, grátis, agora

Crie uma conta e a chave aparece na tela em uns quinze segundos. Ou teste sem conta alguma: o verificador gratuito roda o mesmo motor num arquivo arrastado para a página.

10. Privacidade e determinismo

  • Nada é armazenado. Os bytes enviados são lidos em memória e descartados quando a resposta é escrita. Nenhum documento vai para disco, nenhum conteúdo de documento é logado e não existe etapa de revisão humana. O que fica é uma linha no registro de uso: qual chave, quando, quantos bytes, qual faixa de risco.
  • Nenhum terceiro no caminho da análise. Só CPU, sem modelos de ML, sem fornecedores externos de dados, sem chamadas de saída durante a análise.
  • Determinístico. Bytes idênticos produzem relatório idêntico, fora o id. Fixe fixtures, faça snapshot dos relatórios e teste regressão contra nós — isso é um uso previsto do plano gratuito, e se um bump de versão mudar um score, preferimos que você perceba antes de nós.
  • Retenção. As linhas de registro de uso de chamadas anônimas (que carregam um endereço IP) são apagadas depois de 90 dias; as com chave, depois de 400 dias.

11. Nossa postura quanto ao disclaimer

Todo relatório carrega esta frase, e ela é a posição real do produto, não enfeite jurídico (o motor a devolve em inglês; a tradução vem depois):

Tamperlens reports risk signals, not authenticity verdicts. Signals can have benign causes; combine them with your own decision logic.

A Tamperlens reporta sinais de risco, não veredictos de autenticidade. Sinais podem ter causas legítimas; combine-os com a sua própria lógica de decisão.

Concretamente, com o que isso nos compromete:

  • Nenhum veredicto NOSSO. Não existe "fraud": true, não existe booleano "authentic", e não vai existir. O relatório descreve o que é verdade sobre os bytes e para por aí.
    A política de veredicto não é exceção, e vale ser exato sobre o porquê: aquele bloco só aparece quando você manda uma regra, é calculado a partir de cortes que você escolheu, e não muda nenhuma nota, nenhuma faixa e nenhum sinal. É o seu julgamento devolvido com as entradas à mostra — o oposto de um fornecedor decidindo por você, e a razão de o código que o avalia estar deliberadamente fora do motor.
  • Causas legítimas são documentadas por sinal, no guia de campo, no mesmo nível de detalhe das maliciosas. Um PDF legitimamente salvo de novo vai disparar sinais. Isso não é bug; é o que "este arquivo foi modificado após a criação" significa.
  • Roteie, não recuse automaticamente. Mapeie faixas para filas de análise. Qualquer desenho que recuse uma pessoa por um único sinal estrutural vai recusar clientes reais que abriram o extrato no Preview.
  • Limites declarados. Sem OCR, sem parsing de transações, sem aritmética de saldo, sem checagem de identidade ou de titularidade da conta, sem validação criptográfica de assinatura e sem capacidade de detectar uma falsificação limpa gerada de uma vez a partir de um template falso. Um adversário determinado que conheça histórico de revisões consegue achatá-lo.

Próximos passos