atrusDatrus
Soporte · Guía de integración

Conectar tu aplicación al Anonymizer

Todo lo que hay que saber para integrar, y también lo que el appliance no hace. Sirve igual si eres el equipo de desarrollo del cliente o el partner que implementa.

¿Buscas qué es el producto y por qué existe? La página del Anonymizer lo cuenta desde el principio.

Cómo conectar

El appliance es compatible con la API de OpenAI. Tu aplicación cambia la URL base y agrega el header X-Datrus-Key. La llave del proveedor de IA sigue siendo tuya y viaja igual que siempre: Datrus la reenvía y no la guarda.

Python · SDK de OpenAI
import os
from openai import OpenAI

cliente = OpenAI(
    # Lo único que cambia de tu integración actual: la URL base.
    base_url="https://anonymizer.tu-empresa.cl/v1",
    # Tu llave del proveedor. El appliance la reenvía tal cual y no la guarda.
    api_key=os.environ["OPENAI_API_KEY"],
    default_headers={
        "X-Datrus-Key": os.environ["DATRUS_KEY"],       # dtr_live_…
        "X-Datrus-User": "ana.soto@tu-empresa.cl",      # quién está usando tu app
    },
)

respuesta = cliente.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "El titular es Ana Soto, RUT 12.345.678-5."}],
)

La misma petición, en HTTP crudo, para verificar la integración antes de tocar código:

curl
curl https://anonymizer.tu-empresa.cl/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "X-Datrus-Key: $DATRUS_KEY" \
  -H "X-Datrus-User: ana.soto@tu-empresa.cl" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [
      {"role": "user", "content": "El titular es Ana Soto, RUT 12.345.678-5."}
    ]
  }'

Los dos dialectos

El appliance entiende dos formas de pedirle algo a un modelo. Comparten todo lo demás —inquilinos, bóveda, asientos, pánico, bitácora y medición— y solo cambian en qué campos son texto libre y cómo se autentica el proveedor.

API de OpenAI

POST /v1/chat/completions

Para quien usa el SDK de OpenAI o cualquier cliente compatible. La llave del proveedor va en Authorization.

Úsalo si tu aplicación ya habla ese dialecto, o si estás partiendo de cero: es el que más clientes y librerías entienden.

Gemini nativo

POST /v1beta/models/{modelo}:generateContent

Para quien usa google-genai y no quiere tocar su código. La llave del proveedor va en x-goog-api-key.

Úsalo si ya tienes una integración con Gemini andando. Existe porque pedirte migrar al SDK de OpenAI —reescribir cómo codificas archivos, en cada servicio— sería hacerte pagar una limitación nuestra.

Python · google-genai
import os

from google import genai
from google.genai.types import HttpOptions

cliente = genai.Client(
    api_key=os.environ["GEMINI_API_KEY"],  # viaja como x-goog-api-key
    http_options=HttpOptions(
        # Sin /v1beta: el SDK arma la ruta completa
        # /v1beta/models/{modelo}:generateContent.
        base_url="https://anonymizer.tu-empresa.cl",
        headers={
            "X-Datrus-Key": os.environ["DATRUS_KEY"],
            "X-Datrus-User": "ana.soto@tu-empresa.cl",
        },
    ),
)

respuesta = cliente.models.generate_content(
    model="gemini-2.5-flash",
    contents="El titular es Ana Soto, RUT 12.345.678-5.",
)
curl · dialecto Gemini
curl "https://anonymizer.tu-empresa.cl/v1beta/models/gemini-2.5-flash:generateContent" \
  -H "Content-Type: application/json" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "X-Datrus-Key: $DATRUS_KEY" \
  -H "X-Datrus-User: ana.soto@tu-empresa.cl" \
  -d '{
    "contents": [
      {"role": "user", "parts": [{"text": "El titular es Ana Soto, RUT 12.345.678-5."}]}
    ]
  }'

Streaming

Soportado en los dos dialectos: stream: true en el de OpenAI y streamGenerateContent en el de Gemini.

Python · streaming con OpenAI
flujo = cliente.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Resume la ficha del titular."}],
    stream=True,
)

for trozo in flujo:
    print(trozo.choices[0].delta.content or "", end="", flush=True)
Python · streaming con Gemini
flujo = cliente.models.generate_content_stream(
    model="gemini-2.5-flash",
    contents="Resume la ficha del titular.",
)

for trozo in flujo:
    print(trozo.text or "", end="", flush=True)

Por qué esto no es gratis

La respuesta llega en trozos, y el modelo no sabe nada de nuestros tokens: puede partir uno justo al medio. El primer trozo trae El RUT del titular es ⟪CHILEAN_RU y el segundo, T·k7f3q9x2mp⟫ y su correo…

El appliance retiene la cola del flujo hasta poder decidir si eso es un token o texto normal, y recién ahí emite. Sin eso, la persona vería ⟪CHILEAN_RUT·k7f3q9x2mp⟫ en pantalla: el dato queda protegido, pero la aplicación se ve rota.

Una consecuencia práctica para medir: en streaming la latencia que reporta la consola es hasta el primer byte. Lo que el modelo tarde en terminar de escribir depende de cuánto escriba, y no es un tiempo nuestro.

X-Datrus-User es obligatorio

Va en cada petición, con el correo o el id de la persona que está usando tu aplicación. No es el id de tu servicio ni una constante: es la persona.

  • Es lo que cuenta los asientos

    El appliance factura por personas distintas en el periodo, no por peticiones. Si mandas siempre el mismo valor, tu consumo se ve como un solo asiento y el número deja de significar algo.

  • Se guarda solo su huella

    El valor se convierte en una huella HMAC con el inquilino mezclado y nunca se almacena en claro. Datrus puede contar personas distintas sin poder nombrar a ninguna.

  • Sin el header, la petición se rechaza

    Responde 400 con un mensaje que dice exactamente qué mandar. Es preferible fallar en la primera prueba de integración a descubrir tres meses después que nunca se contó a nadie.

Las dos familias de llave

Son dos autoridades distintas y no se pueden intercambiar. Las dos viajan en el mismo header, X-Datrus-Key, pero cada endpoint exige la suya.

dtr_…

Llave de conexión

La configura tu aplicación y vive en su variable de entorno. Manda tráfico. No abre la consola y no puede apretar el pánico.

dtc_…

Llave de consola

La usa una persona. Abre la consola, muestra consumo y asientos, y aprieta el pánico. No sirve para enviar tráfico.

Están separadas porque una sola llave, puesta en la variable de entorno de una aplicación, la conoce cualquiera que despliegue — y esa misma llave podía desactivar la protección de la empresa entera. La autoridad de apagar no puede viajar en el lugar menos protegido del sistema.

Prueba y producción

Cada llave pertenece a un ambiente, y los ambientes tienen bóvedas separadas y medición separada. Un token emitido en prueba no existe en producción: si lo mandas allá, no se rehidrata.

Separarlos evita las dos confusiones caras: que una prueba de carga en staging aparezca como consumo real en tu factura, y que un dato de prueba y uno real compartan espacio de tokens.

El viaje recomendado

  1. 1

    Prueba + observación

    Conectas y miras. Nada cambia en tu tráfico: solo se registra qué se habría tokenizado.

  2. 2

    Prueba + aplicación

    Ahora sí tokeniza, pero contra datos de prueba. Acá aparecen los falsos positivos que molestan a tu aplicación.

  3. 3

    Producción + observación

    Tráfico real, sin modificar nada. Es la semana que dice qué datos circulan de verdad.

  4. 4

    Producción + aplicación

    El estado final. Se enciende sabiendo lo que va a pasar, no esperando a ver.

Cada salto cambia una sola variable. Saltar directo de prueba a producción con tokenización activa mezcla dos preguntas —¿funciona mi integración? ¿molesta la tokenización?— y cuando algo falla no se sabe cuál de las dos fue.

Observación y aplicación

El modo se configura por cliente y decide si el appliance modifica el tráfico o solo lo mira.

log_only

Observación

Detecta y registra, pero no modifica nada: el prompt sale tal cual lo escribió tu usuario. Es el modo con que se da de alta un cliente nuevo, para ver una semana de tráfico real antes de cambiarle el comportamiento a su aplicación.

En observación la consola dice «esto se tokenizaría». Nunca «esto se protegió», porque no se protegió: decirlo convertiría la bitácora en una afirmación falsa justo donde alguien la va a citar.

enforce

Aplicación

Tokeniza antes de salir. El modelo recibe el texto con tokens en lugar de datos personales, y la respuesta se rehidrata dentro de tu perímetro.

Es el estado normal de un cliente en operación. Cambiar de modo no requiere redesplegar tu aplicación: es configuración del appliance.

Qué pasa con cada tipo de contenido

La sección que hay que leer entera antes de integrar. Lo que sigue es lo que el appliance hace hoy, no lo que está planificado.

Texto del prompt

Se tokeniza

Se detecta y se tokeniza antes de salir.

Todo lo que viaja como texto libre —los mensajes, la instrucción de sistema, las descripciones de tus herramientas, los argumentos que reenvías del historial— pasa por el detector. Es donde el producto funciona completo: el modelo nunca ve el dato real y la respuesta vuelve rehidratada dentro de tu perímetro.

Imágenes: cédula, selfie, foto de un documento

Pasa sin revisar

Pasan enteras al modelo. No se revisan y no se modifican.

En un flujo de verificación de identidad eso es lo correcto y no una concesión: el modelo tiene que poder leer la cédula, y tacharla rompería tu producto. Lo que sí hacemos es contarlas y registrarlas como adjuntos sin revisar, para que la bitácora no diga «se revisó y venía limpio» sobre algo que nadie miró.

PDF, Word, Excel, PowerPoint

Depende de cómo lo mandes

Depende de cómo los mandes. Es la bifurcación que decide tu garantía.

Si el archivo va como adjunto binario o base64, pasa entero igual que una imagen: se cuenta, no se revisa. Si tu aplicación extrae el texto de su lado y lo manda en el prompt, ese texto se protege completo, como cualquier otro texto. Hoy la diferencia entre las dos garantías la decides tú, en tu código.

Audio

Pasa sin revisar

Pasa entero, por la misma razón.

Un audio en base64 no es texto: mandarlo al detector no encontraría nada y costaría una fortuna. Se cuenta como adjunto sin revisar y se registra.

Lo que todavía no existe

La extracción automática del texto de un documento o de una imagen —el OCR y su tokenización— es trabajo pendiente, no una capacidad actual. Existe en nuestro laboratorio y no está en el camino de producción. Lo decimos acá porque un hueco que falla en silencio es peor que un hueco declarado: un PDF con quinientos RUT pasaría entero y la bitácora anotaría «se revisó y no había nada» sobre algo que nadie miró. Por eso los adjuntos se cuentan y se registran como sin revisar.

El botón de pánico

Datrus se mete en medio de una aplicación que ya funcionaba. Si algo nuestro la rompe, tienes que poder apartarnos en segundos sin escribirle a nadie — si hay que abrir un ticket, no es un botón de pánico.

Quién puede apretarlo
Una persona con la llave de consola (dtc_). La llave de conexión de tu aplicación no puede.
Qué hace exactamente
Paso directo: la petición se reenvía al proveedor sin detección, sin tokenización y sin bóveda. No es «modo observación» — observar también cuesta latencia, y quien aprieta esto puede estar huyendo justamente de eso.
Qué queda registrado
Cuándo se activó, quién y por qué, y cuándo se desactivó. Es por inquilino: en un appliance compartido, tu pánico no desprotege a nadie más. Y sobrevive al reinicio del contenedor.

Cada petición que pasa con el pánico apretado se registra como passthrough, nunca como allowed. No son lo mismo: allowed significa que se revisó y no había nada que redactar; passthrough, que no se revisó. Fundirlos haría que una fiscalización leyera «limpio» donde corresponde leer «sin revisar».

Apretarlo no es un error tuyo: es la válvula que hace aceptable que nos pongamos en el camino. Que se use es información, no una falta — pero «estuvimos tres días en paso directo sin que nadie lo notara» sí es el incidente que hay que poder ver, y por eso queda en el historial.

Rehidratación: qué significa para tu aplicación

Solo se revierten los tokens emitidos en esa misma petición. Los tokens salen de tu perímetro —van al proveedor, a sus registros, a historiales de chat—; sin esta regla, cualquiera podría mandarnos uno de vuelta y recibir el dato real de otra persona.

Un RUT que el modelo leyó de una imagen vuelve real

Ese RUT no lo tokenizamos nosotros: venía dentro de una foto que pasó entera. Cuando el modelo lo transcribe en su respuesta, no es un token nuestro y por lo tanto no se toca — llega a tu backend tal cual, y tu backend puede compararlo con lo que tiene en su base.

Es la razón por la que un flujo de verificación de identidad sigue funcionando con Datrus en medio: el modelo percibe, tu backend decide.

El modelo puede comparar tokens, pero no validarlos

El token es determinista: dos apariciones del mismo RUT producen el mismo token, aunque en un documento venga con puntos y en otro sin ellos. Así que «¿estos dos documentos hablan de la misma persona?» sigue funcionando sin que el modelo vea un solo dato real.

Lo que no puede es validar el dígito verificador: el token no lo conserva. Si tu flujo necesita esa respuesta, tiene que darla tu backend.

La regla corta para decidir si Datrus te sirve o te estorba: si el modelo percibe y tu backend decide, Datrus es invisible. Si le pides al modelo que decida sobre el valor real —validar, buscar en un padrón, escribirle a alguien— hay que conversarlo antes de integrar.

Dónde corre el appliance

Dos formas de entrega, el mismo software: un contenedor de 1 vCPU y 1 GB con la detección y el proxy en la misma imagen. Lo que cambia no es qué hace, sino quién puede ver qué.

Alojado por Datrus

Nosotros lo operamos. Es lo más rápido para partir: apuntas la URL base y estás andando. El contenido de las peticiones se procesa en el appliance y no se almacena, pero corre en infraestructura nuestra.

En tu perímetro

En tu red, tu VPC o tu datacenter. El contenido completo se mira en tu consola y no sale de ahí; Datrus recibe solo metadata. Necesita salida hacia el proveedor de IA que elijas — el modelo de detección viaja dentro de la imagen.

Qué ve Datrus y qué no

Una herramienta de privacidad no puede ser el lugar donde se filtren los datos que protege. Lo que sale del appliance hacia nosotros está declarado campo por campo, con lista blanca explícita.

Lo que recibimos

  • Cuándo ocurrió la petición, hacia qué proveedor y qué modelo
  • Cuántos datos de cada tipo se encontraron — el conteo, no los valores
  • Cuántos adjuntos pasaron sin revisar
  • Latencia, si fue streaming y con qué resultado terminó
  • La huella HMAC de cada persona, para contar asientos sin poder nombrar a ninguna

Lo que no recibimos nunca

  • El texto del prompt ni un carácter de él
  • La respuesta del modelo
  • Los valores detectados: ni el RUT, ni el nombre, ni el correo
  • El identificador de la persona que usó tu aplicación
  • Tus adjuntos, ni las llaves de tu proveedor de IA

La bóveda tampoco es una bodega consultable: los valores se guardan cifrados con AES-256-GCM bajo una llave que es tuya, y los tokens se derivan con HMAC-SHA256. Datrus no conoce esa llave ni la necesita.

Solución de problemas

Los cuatro que aparecen de verdad en una integración, y qué significan.

403 · «X-Datrus-Key inválida» con una llave recién emitida

Espera treinta segundos y reintenta. El appliance refresca su configuración cada 30 s y no consulta a Datrus en cada petición: si lo hiciera, una caída nuestra sería una caída de tu aplicación. El precio de esa independencia es que una llave nueva tarda hasta media ventana en empezar a servir.

Si el mensaje dice «Esta es una llave de consola: no sirve para enviar tráfico», el problema es otro: cambiaste las llaves de lugar. Ve a Las dos familias de llave.

400 · Falta X-Datrus-User

El header es obligatorio en cada petición. El mensaje trae la instrucción completa, a propósito: quien lo lee está integrando y necesita saber qué mandar, no que le digan que algo falta.

Respuesta
HTTP/1.1 400 Bad Request

{
  "error": {
    "code": 400,
    "message": "Falta el header X-Datrus-User. Mándalo en cada petición con el
                correo o el id de la persona que está usando tu aplicación: es
                lo que cuenta los asientos. Guardamos solo su huella, nunca el
                valor.",
    "status": "INVALID_ARGUMENT"
  }
}

503 · El detector no está disponible

Es deliberado y se llama fail closed: si el detector no pudo mirar el texto, no se reenvía nada. Un detector caído nunca se traduce en datos saliendo sin revisar. Reintenta con espera; si persiste, es un problema del appliance y hay que mirarlo, no rodearlo.

Respuesta
HTTP/1.1 503 Service Unavailable

{
  "error": {
    "message": "El detector de PII no está disponible; la solicitud no se
                reenvió (fail closed).",
    "type": "datrus_detector_unavailable"
  }
}

413 · Cuerpo muy grande

La petición excedió el límite de tamaño del appliance (25 MB por defecto, configurable). El corte ocurre antes de tocar nada, así que no hay riesgo de que una parte del cuerpo haya viajado. Casi siempre es un adjunto en base64: revisa si ese archivo tiene que ir completo, o si te conviene extraer el texto en tu lado — que además es la única forma de que quede protegido.

Preguntas frecuentes

Las que llegan durante una integración, respondidas por lo que el appliance hace hoy.

¿Te falta algo para integrar?

Si tu caso no calza con nada de esta guía, probablemente sea un caso que vale la pena conversar antes de escribir código.

Hablemos de tu caso