Alguém envia uma foto para o seu produto e afirma que uma câmera a tirou. Seu backend pode provar ou refutar isso? Os geradores de imagem agora produzem resultados que parecem reais para um revisor humano, então “confiar nos olhos” deixou de funcionar há um tempo. A boa notícia é que você não precisa treinar seu próprio modelo para entregar uma resposta útil. Você pode combinar dois sinais independentes, um manifesto de proveniência criptográfica e um classificador de aprendizado de máquina, em um único veredito que é mais honesto do que qualquer um dos sinais isoladamente.
Este tutorial aborda a construção desse backend como um único serviço com um endpoint POST /verify. Você o alimenta com uma imagem, e ele retorna um veredito JSON com uma pontuação de confiança e os detalhes de proveniência encontrados. Usaremos Python e FastAPI para o servidor, as ferramentas C2PA de código aberto para o sinal de proveniência e uma API de detecção hospedada para o sinal do classificador. Como este é um projeto de API, também projetaremos o contrato do endpoint primeiro e usaremos o Apidog para simular e testá-lo, para que sua equipe de frontend possa começar a integrar antes que o código do backend seja finalizado.
TL;DR
Você construirá um serviço FastAPI expondo POST /verify que aceita um upload de imagem, extrai e valida seu manifesto C2PA Content Credentials com a biblioteca c2pa-python, chama um classificador de detecção de IA hospedado como um segundo sinal independente, e retorna um único veredito JSON (provavelmente_autêntico, provavelmente_ia ou incerto) com uma pontuação de confiança e os detalhes brutos de proveniência. Você também projetará o esquema OpenAPI para o endpoint e usará o Apidog para gerar um servidor mock e executar testes de endpoint contra ele.
Por que dois sinais em vez de um
Antes de qualquer código, ajuda ter clareza sobre o que você está detectando. Não há uma única propriedade de um arquivo que diga “um humano fez isso” ou “uma IA fez isso”. Em vez disso, há pistas, e cada pista detecta um tipo diferente de imagem, enquanto ignora outras.
A primeira pista é a proveniência. C2PA, a Coalition for Content Provenance and Authenticity, é um padrão aberto que anexa metadados à prova de adulteração e criptograficamente assinados a um arquivo de mídia. Esse pacote de metadados é chamado de manifesto, e o nome para o usuário é Content Credentials. Quando uma ferramenta participante, uma câmera, um editor ou um gerador de imagem, cria ou altera uma imagem, ela pode escrever um manifesto que registra o que aconteceu e o assina com um certificado. Se você puder ler e validar esse manifesto, obterá uma declaração forte e verificável sobre o histórico da imagem.
O problema: C2PA é opt-in, e o manifesto é frágil. Uma captura de tela o remove. A recodificação através de um aplicativo de mensagens o remove. Muitas plataformas removem metadados no upload. Portanto, um manifesto ausente não lhe diz quase nada; não significa que a imagem é falsa, e não significa que é real.
A segunda pista é um classificador estatístico. Um modelo de detecção é treinado em milhões de imagens reais e geradas e aprende os artefatos visuais que os geradores tendem a deixar para trás. Ele funciona em qualquer imagem, com ou sem metadados, mas é probabilístico. Retorna uma probabilidade, não um fato, e pode estar errado, especialmente em imagens fora de sua distribuição de treinamento ou imagens que foram fortemente comprimidas.
Nenhum sinal é suficiente por si só. A proveniência é precisa, mas raramente presente. O classificador está sempre disponível, mas nunca é certo. Combine-os e você obterá um veredito que diz, em efeito, “aqui está o que a criptografia prova, aqui está o que o modelo estima, e aqui está o quão confiante a combinação nos torna”. Esse é o objetivo do design. Se você deseja uma análise mais aprofundada do porquê as abordagens de sinal único falham, nosso artigo sobre por que a detecção de imagem de IA falha cobre os modos de falha em detalhes.
Visão geral da arquitetura
O serviço é pequeno de propósito. Um endpoint, duas chamadas downstream, uma resposta combinada.
┌─────────────────────────────┐
image ──▶ │ FastAPI POST /verify │
│ │
│ 1. validate upload │
│ 2. ┌──────────────────┐ │
│ │ C2PA manifest │ │ provenance signal
│ │ (c2pa-python) │ │
│ └──────────────────┘ │
│ 3. ┌──────────────────┐ │
│ │ classifier API │ │ statistical signal
│ │ (hosted detector) │ │
│ └──────────────────┘ │
│ 4. combine into verdict │
└─────────────────────────────┘
│
▼
JSON verdict + confidence
O Passo 1 verifica se o upload é uma imagem real de um tipo suportado e dentro de um limite de tamanho. O Passo 2 lê o manifesto C2PA localmente; sem chamada de rede, apenas análise e validação de certificado. O Passo 3 envia os bytes da imagem para um classificador hospedado via HTTPS. O Passo 4 mescla os dois resultados com uma pequena função de regras e retorna o veredito.
As duas etapas de sinal são independentes. Isso é importante para o tratamento de erros: se o classificador expirar, você ainda pode retornar um veredito parcial do sinal de proveniência, e vice-versa. Retornaremos a isso na seção de reforço.
Para a stack, Python 3.10 ou mais recente é necessário porque a biblioteca C2PA precisa dele. Você usará FastAPI para a camada web, Uvicorn para executá-lo, python-multipart para uploads de arquivos, httpx para a chamada do classificador externo e c2pa-python para a proveniência.
pip install fastapi "uvicorn[standard]" python-multipart httpx c2pa-python
O sinal C2PA
A Content Authenticity Initiative, sob a organização `contentauth` do GitHub, publica as ferramentas C2PA de código aberto. Há duas partes sobre as quais você ouvirá falar:
c2patool, uma ferramenta de linha de comando para exibir e adicionar manifestos. É útil para inspeção rápida a partir de um terminal. Observe que seu repositório independente agora está arquivado, e a CLI reside dentro do projeto Rustc2pa-rs.c2pa-python, o binding Python para a mesma biblioteca Rust subjacente (c2pa-rs). É isso que seu serviço usará. Ele é publicado no PyPI comoc2pa-pythone instalado compip install c2pa-python.
O caminho de leitura da biblioteca se concentra em um objeto `Reader`. Você o aponta para uma imagem e, em seguida, solicita o armazenamento do manifesto como JSON. Aqui está o núcleo do módulo de proveniência.
# provenance.py
import json
import c2pa
def read_provenance(image_path: str) -> dict:
"""
Read and validate the C2PA manifest from an image.
Returns a normalized dict describing what was found.
"""
try:
with c2pa.Reader(image_path) as reader:
manifest_store = json.loads(reader.json())
except c2pa.C2paError as err:
# ManifestNotFound is the expected case for most images.
if str(err).startswith("ManifestNotFound"):
return {
"has_manifest": False,
"validation": "none",
"detail": "No C2PA manifest present in this image.",
}
# Any other C2paError means the file had C2PA data we could not parse.
return {
"has_manifest": True,
"validation": "error",
"detail": f"Could not parse manifest: {err}",
}
active_label = manifest_store.get("active_manifest")
manifests = manifest_store.get("manifests", {})
active = manifests.get(active_label, {})
# validation_status appears only when there are validation problems.
validation_status = manifest_store.get("validation_status", [])
validation = "valid" if not validation_status else "invalid"
claim_generator = active.get("claim_generator", "unknown")
signature_issuer = active.get("signature_info", {}).get("issuer", "unknown")
return {
"has_manifest": True,
"validation": validation,
"claim_generator": claim_generator,
"signature_issuer": signature_issuer,
"validation_status": validation_status,
"detail": "Manifest read successfully.",
}
Algumas notas sobre o que o código faz. O `Reader` é usado como um gerenciador de contexto para que os recursos subjacentes sejam liberados. `reader.json()` retorna o armazenamento completo do manifesto como uma string JSON; a biblioteca também oferece `reader.detailed_json()` se você quiser o relatório detalhado com cada asserção e ingrediente. O resultado esperado para a maioria dos uploads é um `C2paError` cuja mensagem começa com `ManifestNotFound`, porque a maioria das imagens simplesmente não possui Content Credentials. Trate isso como dado, não como uma falha.
Quando um manifesto está presente, dois campos são os mais importantes para um veredito. A string `claim_generator` informa qual ferramenta escreveu o manifesto, por exemplo, uma string de firmware de câmera ou o nome de uma ferramenta de imagem de IA. O array `validation_status` está vazio quando a assinatura e os hashes são verificados, e é preenchido com códigos de erro quando não são. Um manifesto inválido é um sinal de alerta que merece ser exposto; significa que o arquivo alega um histórico que a criptografia não suporta.
O que este sinal não pode fazer: ele não pode lhe dar um veredito quando não há manifesto, o que ocorre na maioria das vezes. É exatamente por isso que você precisa do segundo sinal.
O sinal do classificador
O classificador é uma API hospedada que pontua a probabilidade de uma imagem ser gerada por IA. Vários fornecedores oferecem isso. Este tutorial usa o Sightengine porque seu modelo de detecção de IA tem uma API HTTP documentada e um formato de resposta claro, mas o padrão é o mesmo para qualquer provedor; você troca a URL, os parâmetros e o campo que você lê. Se você está avaliando opções, nosso resumo das melhores APIs de detecção de imagem de IA compara precisão, preços e cobertura entre os fornecedores.
O endpoint de verificação do Sightengine é `https://api.sightengine.com/1.0/check.json`. Você faz um POST da imagem como `media`, define `models` como `genai` e passa seu `api_user` e `api_secret`. A resposta inclui `type.ai_generated`, uma pontuação de 0 a 1 onde um valor mais alto significa mais provável de ser gerado por IA.
# classifier.py
import httpx
SIGHTENGINE_URL = "https://api.sightengine.com/1.0/check.json"
async def classify_image(
image_bytes: bytes,
filename: str,
api_user: str,
api_secret: str,
timeout_seconds: float = 8.0,
) -> dict:
"""
Send the image to the hosted detector.
Returns a normalized dict with the AI-generated score.
"""
data = {
"models": "genai",
"api_user": api_user,
"api_secret": api_secret,
}
files = {"media": (filename, image_bytes)}
try:
async with httpx.AsyncClient(timeout=timeout_seconds) as client:
response = await client.post(SIGHTENGINE_URL, data=data, files=files)
response.raise_for_status()
payload = response.json()
except httpx.TimeoutException:
return {"available": False, "reason": "classifier_timeout"}
except httpx.HTTPStatusError as err:
return {
"available": False,
"reason": f"classifier_http_{err.response.status_code}",
}
except httpx.HTTPError as err:
return {"available": False, "reason": f"classifier_error: {err}"}
if payload.get("status") != "success":
return {
"available": False,
"reason": payload.get("error", {}).get("message", "unknown_error"),
}
ai_score = payload.get("type", {}).get("ai_generated")
if ai_score is None:
return {"available": False, "reason": "missing_score_in_response"}
return {"available": True, "ai_score": float(ai_score)}
A função é assíncrona para que um classificador lento não bloqueie o loop de eventos. O tempo limite é explícito e curto; oito segundos é um padrão sensato para um endpoint interativo, e você deve ajustá-lo à latência real do seu provedor. Cada caminho de falha retorna `available: False` com um motivo legível por máquina em vez de levantar uma exceção. Isso é deliberado: uma interrupção do classificador deve degradar o veredito, não travar a requisição. A lógica do veredito na próxima seção lê `available` e decide o que fazer.
Trate a pontuação como uma estimativa. Um 0,92 significa “o modelo está razoavelmente seguro”, não “isso é IA comprovada”. Os fornecedores atualizam seus modelos, e a precisão varia por gerador e pela quantidade de compressão que a imagem sofreu antes de chegar a você. Para uma visão mais ampla de como essas ferramentas se comportam na prática, consulte nosso guia sobre como verificar se uma imagem é gerada por IA.
Projetando o contrato /verify
Aqui é a parte onde o Apidog ganha seu lugar. Antes de escrever o manipulador de rota, projete a requisição e a resposta como um esquema OpenAPI. Fazer isso primeiro lhe dá três coisas: uma única fonte de verdade na qual ambas as equipes concordam, um servidor mock que o frontend pode chamar imediatamente, e um conjunto de testes que você pode executar no momento em que o backend existir.
A requisição
POST /verify recebe um corpo `multipart/form-data` com um campo, `image`, o arquivo a ser verificado. Mantenha-o simples assim. Parâmetros de consulta opcionais podem vir depois.
A resposta
A resposta é onde o trabalho de design compensa. Ela deve mostrar o veredito final, a confiança e ambos os sinais brutos para que um chamador possa auditar a decisão. Aqui está o formato.
{
"verdict": "likely_ai",
"confidence": 0.86,
"signals": {
"provenance": {
"has_manifest": true,
"validation": "valid",
"claim_generator": "SomeImageTool/2.1",
"signature_issuer": "Some Issuing CA"
},
"classifier": {
"available": true,
"ai_score": 0.91
}
},
"explanation": "Um manifesto C2PA válido nomeia uma ferramenta de imagem de IA, e o classificador pontuou a imagem como provavelmente gerada por IA.",
"checked_at": "2026-05-21T09:30:00Z"
}
verdict é um de três valores de string: provavelmente_autêntico, provavelmente_ia ou incerto. Três valores, não dois, porque a honestidade importa; quando os sinais discordam ou são ambos fracos, “incerto” é a resposta correta. `confidence` é um float de 0 a 1 descrevendo quão fortemente os sinais suportam esse veredito. `signals` carrega ambas as entradas brutas para que o chamador possa mostrar sua própria UI ou aplicar sua própria política. `explanation` é uma frase legível por humanos para a equipe de suporte e logs.
Expressar isso como um esquema OpenAPI é direto. Aqui está o componente de resposta que você colocaria em sua especificação.
components:
schemas:
VerifyResponse:
type: object
required: [verdict, confidence, signals, checked_at]
properties:
verdict:
type: string
enum: [provavelmente_autêntico, provavelmente_ia, incerto]
confidence:
type: number
format: float
minimum: 0
maximum: 1
signals:
type: object
properties:
provenance:
type: object
properties:
has_manifest: { type: boolean }
validation:
type: string
enum: [valid, invalid, error, none]
claim_generator: { type: string }
signature_issuer: { type: string }
classifier:
type: object
properties:
available: { type: boolean }
ai_score:
type: number
format: float
explanation: { type: string }
checked_at: { type: string, format: date-time }
Você pode criar este esquema diretamente no designer visual do Apidog ou importar um arquivo OpenAPI existente. Projetar a API antes da implementação é um fluxo de trabalho que vale a pena adotar em geral; nosso passo a passo do modo spec-first mostra como fazer isso de ponta a ponta no Apidog.
Visita guiada pelo código
Agora as peças se encaixam. Abaixo está o aplicativo FastAPI: validação de entrada, ambas as chamadas de sinal, a função de combinação e a rota.
Combinando os dois sinais
A função de veredito é o coração do serviço. Ela codifica sua política. A proveniência, quando válida e presente, é o sinal mais forte porque é criptográfica; o classificador é um desempate e um fallback. Aqui está uma versão clara e conservadora.
# verdict.py
def combine_signals(provenance: dict, classifier: dict) -> dict:
"""Merge the provenance and classifier signals into one verdict."""
has_manifest = provenance.get("has_manifest", False)
validation = provenance.get("validation", "none")
generator = (provenance.get("claim_generator") or "").lower()
classifier_ok = classifier.get("available", False)
ai_score = classifier.get("ai_score")
# Heurística: ferramentas de IA conhecidas tendem a se identificar no manifesto.
ai_keywords = ("firefly", "dall-e", "dalle", "midjourney", "stable",
"gpt", "gemini", "imagen", "generat")
generator_looks_ai = any(k in generator for k in ai_keywords)
# Caso 1: um manifesto válido que nomeia um gerador de IA. Sinal forte de IA.
if has_manifest and validation == "valid" and generator_looks_ai:
return _verdict("provavelmente_ia", 0.95,
"Um manifesto C2PA válido nomeia uma ferramenta de imagem de IA.")
# Caso 2: um manifesto válido de uma câmera ou editor não-IA. Sinal autêntico forte.
if has_manifest and validation == "valid" and not generator_looks_ai:
if classifier_ok and ai_score is not None and ai_score > 0.85:
return _verdict("incerto", 0.55,
"O manifesto parece autêntico, mas o classificador "
"discorda; os sinais entram em conflito.")
return _verdict("provavelmente_autêntico", 0.9,
"Um manifesto C2PA válido de uma ferramenta não-IA está presente.")
# Caso 3: um manifesto que falha na validação. Trate como suspeito.
if has_manifest and validation in ("invalid", "error"):
return _verdict("incerto", 0.6,
"A imagem possui um manifesto C2PA que falhou na "
"validação; seu histórico declarado não é verificado.")
# Caso 4: sem manifesto. Recorra inteiramente ao classificador.
if classifier_ok and ai_score is not None:
if ai_score >= 0.7:
return _verdict("provavelmente_ia", round(ai_score, 2),
"Sem dados de proveniência; o classificador pontuou a "
"imagem como provavelmente gerada por IA.")
if ai_score <= 0.3:
return _verdict("provavelmente_autêntico", round(1 - ai_score, 2),
"Sem dados de proveniência; o classificador pontuou a "
"imagem como provavelmente autêntica.")
return _verdict("incerto", 0.5,
"Sem dados de proveniência e a pontuação do classificador é "
"inconclusiva.")
# Caso 5: sem manifesto e sem classificador. Não podemos realmente dizer.
return _verdict("incerto", 0.0,
"Sem dados de proveniência e o classificador estava indisponível.")
def _verdict(verdict: str, confidence: float, explanation: str) -> dict:
return {"verdict": verdict, "confidence": confidence,
"explanation": explanation}
Leia os cinco casos e você poderá ver a política. Um manifesto válido domina. Um manifesto falho é um aviso, não uma prova de falsidade, então ele cai em “incerto”. Um conflito entre um manifesto limpo e uma alta pontuação do classificador também cai em “incerto” em vez de escolher um lado. E quando ambos os sinais estão ausentes, o serviço o declara honestamente com confiança zero, em vez de adivinhar. Você ajustará esses limites para sua própria tolerância a riscos; uma plataforma de conteúdo e uma redação traçariam as linhas de forma diferente.
O aplicativo FastAPI
# main.py
import os
import tempfile
from datetime import datetime, timezone
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from provenance import read_provenance
from classifier import classify_image
from verdict import combine_signals
app = FastAPI(title="API de Detecção de Imagens de IA", version="1.0.0")
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp"}
MAX_BYTES = 12 * 1024 * 1024 # 12 MB
SIGHTENGINE_USER = os.environ.get("SIGHTENGINE_API_USER", "")
SIGHTENGINE_SECRET = os.environ.get("SIGHTENGINE_API_SECRET", "")
@app.post("/verify")
async def verify(image: UploadFile = File(...)):
# 1. Validar o upload.
if image.content_type not in ALLOWED_TYPES:
raise HTTPException(
status_code=415,
detail=f"Tipo não suportado {image.content_type}. "
f"Envie JPEG, PNG ou WebP.",
)
image_bytes = await image.read()
if len(image_bytes) == 0:
raise HTTPException(status_code=400, detail="Arquivo vazio.")
if len(image_bytes) > MAX_BYTES:
raise HTTPException(status_code=413, detail="O arquivo excede o limite de 12 MB.")
# 2. Sinal de proveniência. O leitor C2PA precisa de um caminho de arquivo,
# então escrevemos em um arquivo temporário e o limpamos depois.
suffix = os.path.splitext(image.filename or "")[1] or ".img"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
tmp.write(image_bytes)
tmp_path = tmp.name
try:
provenance = read_provenance(tmp_path)
finally:
os.unlink(tmp_path)
# 3. Sinal do classificador. Falhas retornam available: False, não exceções.
if SIGHTENGINE_USER and SIGHTENGINE_SECRET:
classifier = await classify_image(
image_bytes, image.filename or "upload",
SIGHTENGINE_USER, SIGHTENGINE_SECRET,
)
else:
classifier = {"available": False, "reason": "classifier_not_configured"}
# 4. Combinar e responder.
result = combine_signals(provenance, classifier)
return JSONResponse({
"verdict": result["verdict"],
"confidence": result["confidence"],
"signals": {
"provenance": {
k: provenance.get(k) for k in
("has_manifest", "validation", "claim_generator",
"signature_issuer")
},
"classifier": {
"available": classifier.get("available", False),
"ai_score": classifier.get("ai_score"),
},
},
"explanation": result["explanation"],
"checked_at": datetime.now(timezone.utc).isoformat(),
})
Execute-o localmente com `uvicorn main:app --reload` e o endpoint estará ativo em `http://127.0.0.1:8000/verify`. O leitor C2PA espera um caminho de arquivo, então o manipulador escreve o upload em um arquivo temporário e o exclui em um bloco `finally`; o classificador funciona diretamente com os bytes. Observe que a requisição nunca falha devido a um manifesto ausente ou um classificador indisponível; ambos são estados normais que a função de veredito trata.
Um backend projetado dessa forma, um serviço focado com um contrato limpo, se encaixa na tendência mais ampla de produtos que expõem sua capacidade central através de uma API. Se essa ideia lhe interessa, nosso ensaio sobre software headless vale a leitura.
Testando e simulando com Apidog
Aqui está o problema do fluxo de trabalho: sua equipe de frontend quer construir a UI de upload e o painel de resultados agora, mas o backend acima leva alguns dias para ser finalizado, obter chaves e ser implantado. Você não quer que eles sejam bloqueados. É para isso que servem os servidores mock, e é onde projetar o esquema primeiro compensa.
Gerar um servidor mock a partir do esquema
Importe o esquema OpenAPI para o Apidog, ou construa o endpoint `/verify` no designer visual. O Apidog lê o esquema de resposta e gera um servidor mock automaticamente. Como o esquema define tipos de campo e enums, o mock retorna dados formatados exatamente como o endpoint real: um `verdict` que é um dos três valores de enum, um float `confidence` entre 0 e 1, um objeto `signals` preenchido. O frontend aponta sua chamada `fetch` para a URL mock do Apidog e obtém respostas realistas no primeiro dia. Quando o backend real for lançado, eles mudam uma única URL base.
O mock também é onde você pratica os casos difíceis antes que qualquer código real exista. Defina respostas de exemplo para os vereditos que importam:
- uma resposta
provavelmente_autênticocom um manifesto de câmera válido, - uma resposta
provavelmente_iacom uma ferramenta de IA nomeada no manifesto, - uma resposta
incertoonde o classificador estava indisponível, - as respostas de erro
415e413.
O frontend pode construir e estilizar cada estado, incluindo os estados de erro, contra o mock. É assim que você lança uma UI e uma API em paralelo, em vez de em sequência.
Executar testes de endpoint no Apidog
Assim que o backend estiver em execução, crie uma requisição no Apidog para `POST /verify`. Defina o método, aponte-o para sua URL local e, na aba Body, escolha `form-data`, adicione o campo `image`, defina seu tipo como File e escolha uma imagem de teste do disco.
Envie, e o Apidog mostrará a resposta JSON. Agora adicione asserções para que isso se torne uma verificação repetível em vez de um clique único:
- garantir que o status da resposta seja
200, - garantir que
verdictexista e seja uma das três strings permitidas, - garantir que
confidenceseja um número entre 0 e 1, - garantir que
signals.provenance.has_manifestseja um booleano.
Construa um pequeno cenário de teste que execute vários uploads em sequência: uma imagem com Content Credentials, um JPEG simples sem manifesto, um arquivo muito grande e um arquivo não-imagem renomeado com a extensão `.jpg`. Cada um verifica um ramo diferente da sua lógica de veredito e da sua validação de entrada. Salve o cenário e você poderá reexecutar todo o conjunto após cada alteração, ou conectá-lo ao CI para que uma regressão na função de veredito falhe na construção. Testar um endpoint de upload manualmente com `curl` fica chato rapidamente; um cenário salvo não.
Reforço e casos limite
O caminho feliz são os fáceis 80 por cento. Um serviço de verificação vive ou morre nos outros 20 por cento, porque as entradas são, por natureza, adversariais; alguém está tentando fazer passar uma imagem por algo que não é.
- Arquivos corrompidos ou truncados. Um arquivo pode ter um tipo MIME de imagem e ainda ser lixo. O leitor C2PA levanta um
C2paErrorem dados que não pode analisar, e a funçãoread_provenancejá transforma isso em um resultado limpo em vez de um 500. Para maior segurança, você pode decodificar a imagem com uma biblioteca como Pillow antes de processar e rejeitá-la com um400se a decodificação falhar. Isso também bloqueia o truque do arquivo de texto renomeado. - Manifesto ausente. Coberto, mas vale a pena reafirmar porque é o caso mais comum e o mais fácil de errar. Nenhum manifesto não é um erro e não é um veredito. A biblioteca sinaliza com um
C2paErrorcuja mensagem começa comManifestNotFound; o serviço captura isso especificamente e segue para o classificador. Nunca deixe um manifesto ausente produzir um 500 ou um veredito “falso”. - Tempo limite ou interrupção do classificador. O classificador é uma dependência de rede de terceiros, então assuma que ele falhará às vezes. A função
classify_imageusa um tempo limite explícito dohttpxe retornaavailable: Falseem qualquer tempo limite ou erro HTTP. A função de veredito então recorre apenas à proveniência, ou retornaincertocom confiança zero se também não houver manifesto. O endpoint ainda responde com um200e um veredito honesto; um fornecedor lento não pode derrubar seu serviço. - Manifestos falsificados. Um manifesto pode estar presente, mas inválido, assinado com um certificado ruim ou com hashes que não correspondem aos pixels. Este é o caso que as pessoas esquecem. Sempre verifique
validation_status; um array vazio significa que o manifesto foi verificado, um preenchido significa que não. A função de veredito trata um manifesto falho como um aviso que resulta emincerto, nunca como prova. Confiar em um manifesto não validado é pior do que não ter manifesto algum. - Arquivos grandes e abuso. Limite o tamanho do upload, o exemplo usa 12 MB, e rejeite qualquer coisa maior com um
413antes de ler todo o corpo na memória onde você pode. Coloque um limite de taxa na frente do endpoint; a validação criptográfica e uma chamada de API de saída por requisição não são gratuitas, e um endpoint de verificação aberto é um alvo convidativo. - Privacidade. Você está recebendo imagens de usuários. Processe-as na memória ou em um arquivo temporário que você exclui imediatamente, como o exemplo faz, e não registre os bytes da imagem. Se você estiver enviando imagens para um classificador de terceiros, diga isso em sua política de privacidade e certifique-se de que isso é permitido para seu caso de uso.
O que cada sinal detecta e perde
Esta tabela é o modelo mental a ser mantido. É por isso que o serviço usa ambos.
| Cenário | Sinal de proveniência C2PA | Sinal do classificador |
|---|---|---|
| Imagem de IA de uma ferramenta que escreve Content Credentials | Detecta: manifesto nomeia o gerador | Geralmente detecta: artefatos presentes |
| Imagem de IA com metadados removidos (captura de tela, re-upload) | Perde: nenhum manifesto para ler | Detecta: funciona em pixels, sem necessidade de metadados |
| Foto real de uma câmera que assina Content Credentials | Confirma: manifesto válido, gerador não-IA | Pode ser falso-positivo em compressão pesada ou edições |
| Foto real sem metadados | Sem sinal: nada para validar | Melhor palpite apenas: probabilístico, pode estar errado |
| Imagem com manifesto forjado ou adulterado | Detecta: validation_status sinaliza a falha |
Pode ou não detectar, depende dos pixels |
| Gerador novo no qual o classificador não foi treinado | Detecta apenas se a ferramenta escreve um manifesto | Frequentemente perde: fora da distribuição de treinamento |
| Foto real fortemente editada (retoque de IA em base real) | Manifesto, se presente, registra o histórico de edição | Ambíguo: parcialmente sintético, pontuação fica no meio do intervalo |
Leia cada linha e você verá a mesma história: onde um sinal é cego, o outro frequentemente não é. A proveniência é exata, mas esparsa; o classificador é universal, mas difuso. O veredito combinado é mais confiável do que qualquer coluna sozinha, e o valor honesto `incerto` existe para as linhas onde ambos os sinais são fracos.
Casos de uso no mundo real
Este padrão não é acadêmico. Alguns lugares onde ele se encaixa diretamente:
- Plataformas de conteúdo gerado pelo usuário. Um marketplace ou aplicativo social pode executar uploads através de
/verifye rotular ou enfileirar para revisão qualquer coisa que pontue como provavelmente IA, ou qualquer coisa que contenha um manifesto que falhe na validação. O veredito de três valores mapeia claramente para “permitir”, “sinalizar” e “enviar para um humano”. - Redações e verificação de fatos. Um editor verificando uma imagem viral obtém tanto a proveniência criptográfica, se houver, quanto uma estimativa de modelo independente em uma única chamada, com uma frase de explicação que pode ser citada em suas anotações.
- Seguros e entrada de sinistros. Quando um cliente envia evidências fotográficas, uma etapa de verificação levanta uma bandeira em imagens que parecem geradas ou contêm um manifesto adulterado, antes que um ajustador humano gaste tempo nelas.
- Pipelines de ativos internos. Uma equipe que precisa manter imagens geradas por IA fora, ou claramente rotuladas, de uma biblioteca de estoque pode controlar a ingestão por meio de
/verify. - Publicação com reconhecimento de proveniência. À medida que mais câmeras e editores adotam os Content Credentials, um CMS pode ler o manifesto no upload e exibir um selo verificado, recorrendo ao classificador quando nenhum manifesto estiver presente.
O fio condutor comum: você quer uma primeira passagem rápida e automatizada que seja honesta sobre sua própria incerteza, para que a atenção humana vá para onde é realmente necessária.
Conclusão
Detectar imagens geradas por IA de forma eficaz não se trata de encontrar um teste perfeito. Trata-se de combinar sinais independentes e ser honesto sobre a confiança.
- Os Content Credentials C2PA fornecem um sinal de proveniência forte e criptograficamente verificável, mas o manifesto é opt-in e facilmente removido, então frequentemente está ausente.
- Um classificador hospedado fornece um sinal universal, mas probabilístico, que funciona em qualquer imagem, com ou sem metadados.
- Combiná-los em um pequeno serviço FastAPI produz um veredito de três valores,
provavelmente_autêntico,provavelmente_iaouincerto, com uma pontuação de confiança e ambos os sinais brutos anexados para auditoria. - Projetar o contrato OpenAPI primeiro permite simular o endpoint no Apidog para que o frontend construa em paralelo, e então executar cenários de teste salvos contra o backend real.
- Nenhum detector é perfeito. Dois sinais aumentam a confiança; eles não eliminam a incerteza, e é por isso que o veredito
incertoé uma característica, não uma lacuna.
Para construir isso de verdade, projete o esquema `/verify`, gere um servidor mock e execute seus testes de endpoint em um só lugar. Baixe o Apidog para projetar, simular e testar a API enquanto a constrói, então passe do mock para o backend ativo com uma única mudança de URL base.
