A foto de um passaporte é o arquivo mais sensível que a maioria dos apps chega a receber. Ela traz um rosto, um nome completo, uma data de nascimento e um número de documento, e é o bastante para abrir uma conta em outro lugar. Mesmo assim, em muitos fluxos de upload a imagem é copiada cinco ou seis vezes antes que alguém pergunte se ela precisa existir.

Este post olha a questão pelo lado da engenharia. Ele cita o que o GDPR diz sobre guardar dados, percorre os lugares onde uma imagem de passaporte vai se acumulando sem ninguém perceber e descreve um padrão que chamamos de ler, devolver, esquecer: a imagem é lida, o resultado é devolvido e nada da imagem é guardado. No fim vem a parte que continua sendo trabalho seu, porque algumas empresas são obrigadas a guardar uma cópia, e a partir de julho de 2027 a UE deixa isso explícito numa nova lei.

Este é o blog do doc.cheap, uma API de OCR para passaportes e documentos de identidade que lê documentos de identidade e devolve o resultado como JSON. Leia as partes sobre o produto levando isso em conta.

O que o GDPR pede de fato

O GDPR não diz "nunca guarde a imagem de um passaporte". Ele diz algo mais útil: guarde o que você precisa, pelo tempo que precisar, e não mais do que isso. O art. 5.º, n.º 1, do Reg. (EU) 2016/679 define os princípios. Dois deles decidem a maior parte do design.

Princípio Texto do art. 5.º, n.º 1 (versão em inglês) O que significa para uma imagem
Minimização dos dados, alínea c) "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed" Se o seu processo precisa do nome, da data de nascimento e do número do documento, a imagem em si pode deixar de ser necessária depois que eles forem lidos.
Limitação da conservação, alínea e) "kept in a form which permits identification of data subjects for no longer than is necessary for the purposes for which the personal data are processed" Toda cópia precisa de uma data de fim, e "nunca chegamos a apagar" não é uma.

O art. 5.º, n.º 2, acrescenta a responsabilidade: você precisa conseguir demonstrar que segue esses princípios. E o art. 25.º, n.º 1, sobre proteção de dados desde a concepção, pede "appropriate technical and organisational measures, such as pseudonymisation, which are designed to implement data-protection principles, such as data minimisation, in an effective manner", ou seja, medidas técnicas e organizacionais adequadas, como a pseudonimização, pensadas para aplicar com eficácia princípios como a minimização dos dados.

Lidos juntos, eles transformam uma questão jurídica numa questão de engenharia. Quanto menos cópias de uma imagem existirem, menos lugares você terá de descrever, proteger, incluir no backup e, um dia, esvaziar. Uma cópia que nunca foi gravada é a única que não exige nada desse trabalho.

Onde as imagens de passaporte vão parar

A maioria das equipes guarda a imagem de propósito num único lugar. O problema são os lugares que ninguém escolheu. Aqui vai uma lista para conferir contra o seu próprio fluxo.

Lugar Como a imagem chega lá
Bucket de upload O cliente envia primeiro para um armazenamento de objetos, e o backend lê de lá. O objeto sobrevive à requisição.
Logs de requisição Um middleware de logging grava os corpos das requisições, e uma imagem em base64 é um corpo de requisição.
Relatórios de erro Um rastreador de exceções anexa o payload da requisição que falhou.
Filas e retentativas Uma mensagem de job carrega a imagem, e uma dead-letter queue guarda as que falharam por semanas.
Backups e snapshots Um snapshot do banco de dados ou do disco feito naquele dia contém todas as imagens gravadas antes dele, muito depois de a linha ser apagada.
Tickets de suporte Um usuário manda a foto de novo por e-mail "porque o upload não funcionou".
Analytics e gravação de sessão Uma ferramenta grava a página, incluindo a pré-visualização do arquivo selecionado.
O provedor de OCR O serviço que lê o documento guarda a própria cópia, com as próprias regras de retenção.

A última linha é a que você menos controla. Os seus próprios logs você consegue corrigir. Uma cópia mantida por um fornecedor segue as configurações desse fornecedor, e você precisa perguntar quais são.

O padrão: ler, devolver, esquecer

O padrão é simples de enunciar. A imagem existe só na memória, durante uma requisição. O que sai da requisição é a leitura: os valores extraídos. A imagem não sai de jeito nenhum.

  1. Envie a imagem direto para o reconhecimento. Sem bucket de upload no meio. Se um bucket for inevitável para arquivos grandes, dê ao objeto uma vida de minutos e apague-o quando a chamada retornar.
  2. Use na hora o que você precisa da resposta. Recortes como a foto do titular só existem nessa resposta. Se o seu fluxo compara uma selfie com o retrato, faça isso agora.
  3. Guarde a leitura, não a imagem. Armazene os campos de que o seu processo precisa, sob a sua própria regra de retenção. Uma data de nascimento e um número de documento continuam sendo dados pessoais, então também ganham uma data de fim.
  4. Mantenha a imagem fora dos logs e dos relatórios de erro. Remova os corpos na borda, num único lugar, em vez de confiar que cada chamador vai lembrar.
  5. Registre o resultado. Para cada lugar da tabela acima, anote se a imagem pode chegar até ele e por que não. Essa anotação é a responsabilidade que o art. 5.º, n.º 2, pede.

O que a nossa API faz com a imagem

Veja como o doc.cheap trata a mesma questão, conforme descrito na página de retenção de dados e privacidade.

  • A imagem nunca é guardada. Ela vive na memória durante a requisição, é entregue ao motor de reconhecimento e some quando a resposta é escrita. Nenhum disco, armazenamento de objetos ou log a recebe.
  • Os recortes também não são guardados. O recorte do documento, a foto do titular e a assinatura voltam na resposta da chamada que os gerou. Um scan lido depois via GET /v1/scans/{id} tem todos os campos de imagem como null.
  • O que pode ser guardado é a leitura, e só pelo prazo que você pedir. A opção retain_hours define esse prazo por requisição, de 0 a 8760 horas (um ano). Um valor explícito sempre prevalece sobre a configuração da conta.
  • retain_hours: 0 não grava nada. Não é uma linha que expira na hora: não há linha nenhuma. Não há nada para varrer, nada num backup e nada para exportar. O scan continua contando como scan.
  • O padrão da conta cobre o resto. Quando uma requisição não indica prazo, vale a configuração de histórico da própria conta: 24 horas, 7 dias, 1 mês ou 1 ano. Contas novas começam com 1 ano, para que o painel mostre um histórico. Encurtar a configuração vale também para as linhas já guardadas, cada uma contada a partir do próprio horário de criação.
  • Uma linha retida guarda uma imagem pequena: uma miniatura de no máximo 96 px no lado mais longo e no máximo 16 KiB, exibida no log de operações do painel para que a linha possa ser reconhecida. Ela não pode ser lida pela API. A miniatura vai embora junto com a linha.
  • Um scan pode ser apagado antes do prazo. Uma chave live envia DELETE /v1/scans/{id}, que remove o resultado, a linha do histórico e a miniatura. É definitivo.

O guia para controlar a retenção do histórico mostra as configurações passo a passo, e a nossa página sobre como tratamos os dados traz o resumo.

Aqui está uma chamada sem retenção em Python com requests. A chave sandbox pública sk_sandbox_public está publicada na documentação e não exige cadastro: ela dá 10 documentos reconhecidos grátis por endereço no total, e no máximo 10 requisições por hora. A retenção zero é uma configuração da sua própria conta, então precisa da sua chave live. O sandbox público não é uma conta: ele guarda um registro de cada scan, com a sua imagem pequena, para o log do próprio serviço, então envie a ele uma imagem de teste, nunca um documento real.

import base64
import uuid

import requests

API = "https://api.doc.cheap/v1/scans"
KEY = "sk_sandbox_public"  # a sua própria chave live em produção


def read_and_forget(path):
    with open(path, "rb") as f:
        image = base64.b64encode(f.read()).decode("ascii")
    response = requests.post(
        API,
        headers={
            "Authorization": f"Bearer {KEY}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "image": image,
            # 0: com uma chave live, nada sobre este scan é registrado do lado da API.
            # False: sem recorte do retrato, porque este fluxo não usa.
            "options": {"retain_hours": 0, "return_portrait": False},
        },
        timeout=30,
    )
    response.raise_for_status()
    scan = response.json()
    del image  # a cópia local vai embora assim que a chamada retorna
    if scan["meta"]["status"] != "recognized":
        return None
    # Guarde a leitura de que o seu processo precisa, sob a sua própria regra de retenção.
    return {
        "scan_id": scan["meta"]["id"],
        "document_number": scan["document"]["number"],
        "expiry_date": scan["document"]["expiry_date"],
        "birth_date": scan["holder"]["birth_date"],
        "mrz_status": scan["mrz"]["status"],
    }

A retenção zero tem um custo que vale conhecer antes que ele pegue você de surpresa. Normalmente, um Idempotency-Key permite que uma retentativa devolva o primeiro resultado. Com retain_hours: 0 não existe resultado guardado para devolver, então durante 24 horas uma retentativa com a mesma chave é recusada com HTTP 409 e o código idempotency_replay_unavailable, em vez de ser respondida duas vezes. Trate essa resposta como "a primeira chamada deu certo" e use o resultado que você já tem.

Ler o scan de volta mostra o outro lado do design. Uma chave sandbox não lê nada de volta, seja qual for o id. Enviamos esta requisição com sk_sandbox_public em 5 de outubro de 2026:

curl https://api.doc.cheap/v1/scans/<SCAN_ID> \
  -H "Authorization: Bearer sk_sandbox_public"

Ela voltou com HTTP 404 (a mensagem está encurtada):

{
  "error": {
    "code": "not_found",
    "message": "No scan with id …",
    "docs_url": "https://doc.cheap/docs/errors/not_found"
  }
}

Uma chave live recebe o mesmo 404 para um scan feito com retain_hours: 0, e para qualquer scan depois que o prazo dele passou. Se você está comparando serviços nesse ponto, a comparação de APIs de OCR de passaporte é um bom começo; pergunte a cada um para onde vai a imagem, e não só o que ele devolve.

O que continua sendo trabalho seu

Ler, devolver, esquecer elimina as cópias do lado da API. Não decide o que a sua empresa tem de guardar. Para algumas empresas a resposta é "uma cópia", e a lei diz isso.

A nova lei de combate à lavagem de dinheiro da UE, o Reg. (EU) 2024/1624, se aplica a partir de 10 de julho de 2027. O art. 90 diz: "It shall apply from 10 July 2027, except in relation to obliged entities referred to in Article 3, points (3)(n) and (o), to which it shall apply from 10 July 2029." Ou seja, ele se aplica a partir de 10 de julho de 2027, exceto para as entidades obrigadas do art. 3, pontos (3)(n) e (o), às quais se aplica a partir de 10 de julho de 2029. O art. 77, sobre a conservação de registros, obriga as entidades obrigadas, como bancos e outras empresas financeiras, a guardar:

"a copy of the documents and information obtained in the performance of customer due diligence pursuant to Chapter III, including information obtained through electronic identification means;"

Em outras palavras, uma cópia dos documentos e das informações obtidos na diligência devida sobre o cliente, incluindo as informações obtidas por meios de identificação eletrônica.

O art. 77, n.º 3, define a duração: os registros são "retained for a period of 5 years commencing on the date of the termination of the business relationship", ou seja, guardados por cinco anos a partir do fim da relação de negócios, e depois "obliged entities shall delete personal data upon expiry of the five-year period", isto é, ao fim dos cinco anos os dados pessoais devem ser apagados. O art. 77, n.º 2, permite, sob condições, "a retention of the references to such information" no lugar de cópias, ou seja, guardar apenas as referências a essas informações.

Então, se você é uma entidade obrigada, a retenção zero na API não elimina o seu dever de manter um registro. Ela muda onde o registro fica. O seu próprio armazenamento passa a ser a única cópia, e o princípio da limitação da conservação acima continua valendo para ele: cinco anos depois do fim da relação, ele vai embora. O trabalho de design é tornar esse armazenamento deliberado, com um lugar, um responsável, criptografia, controle de acesso e um job de exclusão, em vez da pilha acidental da tabela acima.

Se você não é uma entidade obrigada, faça primeiro a pergunta simples: alguma coisa no seu processo precisa da imagem depois que os campos foram lidos? Muitas vezes a resposta honesta é não.

Um checklist

  • Cada lugar da tabela "onde as imagens vão parar" foi conferido contra o seu fluxo.
  • A imagem vai direto para o reconhecimento, ou passa por um bucket com vida de minutos.
  • Os recortes são usados dentro do handler da resposta e não são gravados em lugar nenhum.
  • A chamada de OCR define a sua retenção de propósito: retain_hours: 0 quando não há nada para ler de volta.
  • As retentativas tratam a resposta 409 idempotency_replay_unavailable.
  • Logs e relatórios de erro removem os corpos das requisições numa única borda.
  • Os campos que você guarda têm uma data de fim, e algo os apaga.
  • Se uma lei exige uma cópia, ela fica num único armazenamento deliberado, com a própria data de exclusão.

Este é um resumo de engenharia, não aconselhamento jurídico. Se você encontrar um lugar por onde uma imagem pode vazar e que este post não cobre, escreva para admin@doc.cheap.

Uma pergunta para você: onde você encontrou pela última vez uma cópia de um documento de identidade que ninguém pretendia guardar? Conte para a gente nos comentários abaixo.