La foto de un pasaporte es el archivo más sensible que recibe la mayoría de las aplicaciones. Lleva una cara, un nombre completo, una fecha de nacimiento y un número de documento, y basta para abrir una cuenta en otro sitio. Aun así, en muchos flujos de subida la imagen se copia cinco o seis veces antes de que alguien se pregunte si tiene que existir siquiera.

Este artículo mira la cuestión desde el lado de la ingeniería. Cita lo que dice el RGPD sobre conservar datos, recorre los lugares donde una imagen de pasaporte se va acumulando sin que nadie lo note y describe un patrón al que llamamos leer, devolver, olvidar: la imagen se lee, el resultado se devuelve y no se guarda nada de la imagen. Termina con la parte que sigue siendo cosa suya, porque algunas empresas están obligadas a guardar una copia, y desde julio de 2027 la UE lo deja por escrito en una nueva ley.

Este es el blog de doc.cheap, una API de OCR para pasaportes y documentos de identidad que lee documentos de identidad y devuelve el resultado como JSON. Lea las partes sobre el producto teniéndolo en cuenta.

Lo que el RGPD pide en realidad

El RGPD no dice «nunca guarde la imagen de un pasaporte». Dice algo más útil: guarde lo que necesita, durante el tiempo que lo necesita, y no más. El art. 5, apartado 1, del Reg. (EU) 2016/679 fija los principios. Dos de ellos deciden la mayor parte del diseño.

Principio Texto del art. 5, apartado 1 (versión inglesa) Qué significa para una imagen
Minimización de datos, letra c) "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed" Si su proceso necesita el nombre, la fecha de nacimiento y el número de documento, puede que la imagen en sí deje de ser necesaria una vez leídos.
Limitación del plazo de conservación, letra 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" Cada copia necesita una fecha de fin, y «nunca llegamos a borrarla» no lo es.

El art. 5, apartado 2, añade la responsabilidad proactiva: usted debe poder demostrar que cumple estos principios. Y el art. 25, apartado 1, sobre protección de datos desde el diseño, pide "appropriate technical and organisational measures, such as pseudonymisation, which are designed to implement data-protection principles, such as data minimisation, in an effective manner", es decir, medidas técnicas y organizativas adecuadas, como la seudonimización, pensadas para aplicar de forma eficaz principios como la minimización de datos.

Leídos juntos, convierten una pregunta jurídica en una de ingeniería. Cuantas menos copias de una imagen existan, menos lugares tendrá que describir, proteger, respaldar y, al final, vaciar. Una copia que nunca se escribió es la única que no exige nada de ese trabajo.

Dónde acaban las imágenes de pasaporte

La mayoría de los equipos guarda la imagen a propósito en un solo lugar. El problema son los lugares que nadie eligió. Aquí tiene una lista para repasar frente a su propio flujo.

Lugar Cómo llega allí la imagen
Bucket de subida El cliente sube primero a un almacenamiento de objetos y el backend la lee desde allí. El objeto sobrevive a la petición.
Logs de peticiones Un middleware de logging escribe los cuerpos de las peticiones, y una imagen en base64 es un cuerpo de petición.
Informes de errores Un rastreador de excepciones adjunta la carga de la petición que falló.
Colas y reintentos Un mensaje de trabajo lleva la imagen, y una cola de mensajes fallidos (dead-letter) guarda los que fallaron durante semanas.
Copias de seguridad y snapshots Un snapshot de la base de datos o del disco tomado ese día contiene todas las imágenes escritas antes, mucho después de borrar la fila.
Tickets de soporte Un usuario vuelve a enviar la foto por correo «porque la subida no funcionó».
Analítica y grabación de sesiones Una herramienta graba la página, incluida la vista previa del archivo seleccionado.
El proveedor de OCR El servicio que lee el documento guarda su propia copia, con sus propias reglas de conservación.

La última fila es la que menos controla. Sus propios logs los puede arreglar. Una copia en manos de un proveedor depende de la configuración de ese proveedor, y tiene que preguntar cuál es.

El patrón: leer, devolver, olvidar

El patrón es fácil de enunciar. La imagen solo existe en memoria, durante una petición. Lo que sale de la petición es la lectura: los valores extraídos. La imagen no sale en absoluto.

  1. Envíe la imagen directamente al reconocimiento. Sin bucket de subida en medio. Si un bucket es inevitable para archivos grandes, dé al objeto una vida de minutos y bórrelo cuando vuelva la llamada.
  2. Use de inmediato lo que necesite de la respuesta. Los recortes, como la foto del titular, solo existen en esa respuesta. Si su flujo compara un selfie con el retrato, hágalo ahora.
  3. Guarde la lectura, no la imagen. Almacene los campos que necesita su proceso, con su propia regla de conservación. Una fecha de nacimiento y un número de documento siguen siendo datos personales, así que también reciben una fecha de fin.
  4. Mantenga la imagen fuera de los logs y de los informes de errores. Elimine los cuerpos en el borde, en un único lugar, en vez de confiar en que cada llamador se acuerde.
  5. Deje constancia del resultado. Para cada lugar de la tabla anterior, anote si la imagen puede llegar a él y por qué no. Esa nota es la responsabilidad proactiva que pide el art. 5, apartado 2.

Qué hace nuestra API con la imagen

Así es como doc.cheap trata la misma cuestión, tal como la describe su página de conservación de datos y privacidad.

  • La imagen nunca se guarda. Vive en memoria durante la petición, se entrega al motor de reconocimiento y desaparece cuando se escribe la respuesta. Ningún disco, almacén de objetos ni log la recibe.
  • Los recortes tampoco se guardan. El recorte del documento, la foto del titular y la firma vuelven en la respuesta de la llamada que los produjo. Un escaneo leído más tarde con GET /v1/scans/{id} tiene todos los campos de imagen a null.
  • Lo que se puede guardar es la lectura, y solo durante el plazo que usted pida. La opción retain_hours lo fija en cada petición, de 0 a 8760 horas (un año). Un valor explícito siempre prevalece sobre la configuración de la cuenta.
  • retain_hours: 0 no escribe nada. No es una fila que caduca al instante: no hay fila en absoluto. No hay nada que barrer, nada en una copia de seguridad y nada que exportar. El escaneo sigue contando como escaneo.
  • El valor por defecto de la cuenta cubre el resto. Cuando una petición no indica plazo, se aplica la configuración de historial de la propia cuenta: 24 horas, 7 días, 1 mes o 1 año. Las cuentas nuevas empiezan con 1 año, para que el panel muestre un historial. Acortar el plazo se aplica a las filas ya guardadas, cada una contada desde su propia hora de creación.
  • Una fila conservada guarda una imagen pequeña: una miniatura de 96 px como máximo en su lado más largo y de 16 KiB como máximo, que se muestra en el registro de operaciones del panel para poder reconocer la fila. No se puede leer a través de la API. La miniatura desaparece cuando desaparece la fila.
  • Un escaneo se puede borrar antes de tiempo. Una clave live envía DELETE /v1/scans/{id}, que elimina el resultado, la fila del historial y la miniatura. Es definitivo.

La guía para controlar la conservación del historial explica la configuración paso a paso, y nuestra página sobre cómo tratamos los datos ofrece el resumen.

Aquí tiene una llamada sin retención en Python con requests. La clave sandbox pública sk_sandbox_public aparece en la documentación y no requiere registro: da 10 documentos reconocidos gratis por dirección en total, y como máximo 10 peticiones por hora. La retención cero es un ajuste de su propia cuenta, así que necesita su clave live. El sandbox público no es una cuenta: guarda un registro de cada escaneo, con su imagen pequeña, para el registro propio del servicio, así que envíele una imagen de prueba, nunca un documento real.

import base64
import uuid

import requests

API = "https://api.doc.cheap/v1/scans"
KEY = "sk_sandbox_public"  # su propia clave live en producción


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: con una clave live, no se anota nada de este escaneo en el lado de la API.
            # False: sin recorte del retrato, porque este flujo no lo usa.
            "options": {"retain_hours": 0, "return_portrait": False},
        },
        timeout=30,
    )
    response.raise_for_status()
    scan = response.json()
    del image  # la copia local desaparece en cuanto vuelve la llamada
    if scan["meta"]["status"] != "recognized":
        return None
    # Guarde la lectura que necesita su proceso, con su propia regla de conservación.
    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"],
    }

La retención cero tiene un coste que conviene conocer antes de que le sorprenda. Normalmente, un Idempotency-Key permite que un reintento devuelva el primer resultado. Con retain_hours: 0 no hay ningún resultado guardado que devolver, así que durante 24 horas un reintento con la misma clave se rechaza con HTTP 409 y el código idempotency_replay_unavailable, en lugar de responderse dos veces. Tome esa respuesta como «la primera llamada se completó» y use el resultado que ya tiene.

Volver a leer el escaneo muestra el otro lado del diseño. Una clave sandbox no puede leer nada de vuelta, sea cual sea el id. Enviamos esta petición con sk_sandbox_public el 5 de octubre de 2026:

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

Volvió con HTTP 404 (el mensaje está recortado):

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

Una clave live recibe el mismo 404 para un escaneo hecho con retain_hours: 0, y para cualquier escaneo una vez pasado su plazo. Si está comparando servicios en este punto, la comparativa de API de OCR para pasaportes es un buen punto de partida; pregunte a cada uno adónde va la imagen, no solo qué devuelve.

Lo que sigue siendo cosa suya

Leer, devolver, olvidar elimina las copias en el lado de la API. No decide qué tiene que conservar su empresa. Para algunas empresas la respuesta es «una copia», y la ley lo dice.

La nueva ley antiblanqueo de la UE, el Reg. (EU) 2024/1624, se aplica a partir del 10 de julio de 2027. Su art. 90 dice: "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." Es decir, se aplica desde el 10 de julio de 2027, salvo para las entidades obligadas del art. 3, puntos (3)(n) y (o), a las que se aplica desde el 10 de julio de 2029. Su art. 77, sobre la conservación de documentación, obliga a las entidades obligadas, como los bancos y otras empresas financieras, a conservar:

"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;"

En otras palabras, una copia de los documentos y la información obtenidos al aplicar la diligencia debida con el cliente, incluida la información obtenida por medios de identificación electrónica.

El art. 77, apartado 3, fija la duración: los registros quedan "retained for a period of 5 years commencing on the date of the termination of the business relationship", es decir, se conservan cinco años desde el fin de la relación de negocios, y después "obliged entities shall delete personal data upon expiry of the five-year period", o sea, al cumplirse los cinco años hay que borrar los datos personales. El art. 77, apartado 2, permite, bajo condiciones, "a retention of the references to such information" en lugar de copias, es decir, guardar solo las referencias a esa información.

Así que, si usted es una entidad obligada, la retención cero en la API no le quita el deber de conservar un registro. Cambia dónde vive ese registro. Su propio almacén pasa a ser la única copia, y el principio de limitación del plazo de conservación de arriba se le sigue aplicando: cinco años después de que termine la relación, desaparece. El trabajo de diseño consiste en que ese almacén sea deliberado, con un solo lugar, un responsable, cifrado, control de acceso y una tarea de borrado, en vez del montón accidental de la tabla anterior.

Si no es una entidad obligada, hágase primero la pregunta sencilla: ¿hay algo en su proceso que necesite la imagen después de leer los campos? A menudo la respuesta sincera es que no.

Una lista de comprobación

  • Cada lugar de la tabla «dónde acaban las imágenes» se ha revisado frente a su flujo.
  • La imagen va directamente al reconocimiento, o pasa por un bucket con una vida de minutos.
  • Los recortes se usan dentro del manejador de la respuesta y no se escriben en ningún sitio.
  • La llamada de OCR fija su retención a propósito: retain_hours: 0 cuando no hace falta volver a leer nada.
  • Los reintentos gestionan la respuesta 409 idempotency_replay_unavailable.
  • Los logs y los informes de errores eliminan los cuerpos de las peticiones en un único borde.
  • Los campos que guarda tienen una fecha de fin, y algo los borra.
  • Si una ley exige una copia, vive en un único almacén deliberado con su propia fecha de borrado.

Esto es un resumen de ingeniería, no asesoramiento jurídico. Si encuentra un lugar por donde se pueda filtrar una imagen y que este artículo no recoge, escriba a admin@doc.cheap.

Una pregunta para usted: ¿dónde encontró por última vez una copia de un documento de identidad que nadie pretendía guardar? Cuéntenoslo en los comentarios.