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 resolve →
401na 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,Keywordse 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/baselineslista os seus perfis e os incluídos, combuiltInseparando uns dos outros e um índice deconsistencypor dimensão. -
DELETE /api/v1/baselines/:issuerremove um dos seus. Um perfil incluído é compartilhado e responde404. - 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
policyfica 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[].ideseverity— 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ê querincremental-updatescom peso menor esignature-coveragecom 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 sinalstructure-warningscomevidence.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 busye um headerRetry-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
- Referência OpenAPI Especificação legível por máquina e console de teste para cada endpoint.
- Guia de campo (em inglês) As onze famílias de sinais, chaves de evidência, causas legítimas, lógica de severidade.
- Triagem de extratos bancários (em inglês) Como ligar os sinais a uma decisão de crédito ou de onboarding.
- Forense de metadados de PDF (em inglês) O que o motor realmente lê, estrutura por estrutura.