
# Descargar una exportación de auditoría

`GET /v1/audit-exports/{export_id}/download` · permiso `audit-exports:read`

Devuelve un enlace de un solo uso al paquete de un trabajo de exportación completado. Las claves de API (secretas o restringidas) y los usuarios del panel pueden descargar; una descarga desde el panel requiere un inicio de sesión reciente (verificación adicional, con una antigüedad máxima de 5 minutos). Cada llamada escribe un registro `audit_export.downloaded` en el flujo `admin` antes de emitir el enlace.

## Solicitud

| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
| `export_id` | path | sí | ID del trabajo de exportación, con el prefijo `aex_`. |

Un trabajo creado con `include_ip: true` también requiere `audit-events:read_ip` en la clave que descarga.

## Respuesta

La respuesta depende del encabezado `Accept`. Ambas formas incluyen `Cache-Control: no-store`, `Referrer-Policy: no-referrer` y `Vary: Accept`, y ambas escriben el mismo registro `audit_export.downloaded`.

- **`Accept` enumera `application/json`** (de forma explícita, con un `q` distinto de cero; `*/*` no cuenta): `200 OK` con un cuerpo JSON. Use esta forma desde un navegador o cualquier cliente que no pueda leer una redirección de origen cruzado: lea `url` y, después, ábralo.

  | Campo | Tipo | Descripción |
  |---|---|---|
  | `url` | string | Enlace relativo en el mismo host, `/downloads/audit-exports/{environment_id}/{export_id}/{token}`. |
  | `expires_at` | string (date-time) | Momento en que el enlace deja de funcionar (15 minutos después de emitirse). |

- **Cualquier otro `Accept`**: `302 Found` con un cuerpo vacío. `Location` es el mismo enlace relativo.

- El enlace es válido durante 15 minutos y funciona una sola vez: el token se consume en la primera solicitud, incluso si la transferencia se interrumpe. Vuelva a llamar a esta operación para obtener un enlace nuevo.
- El enlace no requiere credenciales; el token es la única credencial. Mantenga `Location` (o `url`) en secreto y no lo registre en logs.
- El archivo se cifra en reposo con la clave del entorno, por lo que no se usa una URL prefirmada de almacenamiento; KeenAuth lo descifra y lo transmite.

Al seguir el enlace se devuelve `200 OK` con `Content-Type: application/x-tar`, `Content-Length` y `Content-Disposition: attachment; filename="audit-export-aex_….tar"`. El paquete tar contiene:

| Archivo | Contenido |
|---|---|
| `records.ndjson.gz` | Cada registro de la cadena de cada hora cubierta, en orden de secuencia, un objeto JSON por línea: los registros coincidentes completos y los demás como marcadores sin contenido (`"omitted": true`), de modo que la cadena de hashes pueda reconstruirse sin revelarlos. |
| `manifest.json` | Filtro, recuentos, hash del archivo y, por cada hora cubierta, el punto de control firmado y la prueba de inclusión; JSON canónico (RFC 8785). |
| `manifest.jws` | Firma ES256 sobre `manifest.json`; verifíquela con las claves públicas de `audit-jwks.json`. |

Si la conexión termina antes de que lleguen `Content-Length` bytes, el paquete está incompleto; descártelo y solicite un enlace nuevo. Un enlace no válido, ya usado o caducado devuelve `404 not_found`.

## Errores

Cada error es un cuerpo `application/problem+json` con un `code` estable.

| HTTP | `code` | Cuándo |
|---|---|---|
| 401 | `unauthenticated` | La clave de API falta, no es válida o fue revocada. |
| 403 | `insufficient_permissions` | La clave no tiene `audit-exports:read`, el trabajo se creó con `include_ip` y la clave no tiene `audit-events:read_ip` (`required_permissions` enumera ambos), o quien llama no es una clave de API ni un usuario del panel. |
| 404 | `not_found` | El trabajo no existe, pertenece a otro entorno o el ID tiene un formato incorrecto. En el enlace: el token no es válido, ya se usó o caducó. |
| 409 | `conflict` | El trabajo aún no está completado o su archivo ha caducado. |
| 429 | `rate_limited` | Se superó el límite de uso; respete `Retry-After`. |
| 503 | `service_unavailable` | Una dependencia no está disponible temporalmente; vuelva a intentarlo más tarde. |

## Ejemplo

```bash
curl -L -o audit-export.tar \
  https://api.eu.keenauth.com/v1/audit-exports/aex_01j9xk3m7q8r2s4t6v8w0y2z4e/download \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"
```

Con `Accept: application/json` el enlace se devuelve como JSON en lugar de una redirección:

```bash
curl https://api.eu.keenauth.com/v1/audit-exports/aex_01j9xk3m7q8r2s4t6v8w0y2z4e/download \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Accept: application/json"
```

Los SDK usan la forma JSON y devuelven `url` y `expires_at`. La CLI lee el enlace por sí misma, escribe el paquete en un archivo (nunca en la salida estándar), se niega a sobrescribir un archivo existente e imprime el tamaño y el SHA-256 de lo que escribió:

```bash
keenauth audit-exports download aex_01j9xk3m7q8r2s4t6v8w0y2z4e --file audit-export.tar
```

## Datos personales

Este endpoint contiene datos personales: eventos de auditoría. [Datos personales en las API de KeenAuth](/docs/policies/personal-data) describe qué se almacena, cuánto tiempo se conserva y cómo se elimina. No envíe categorías especiales de datos personales (salud, datos biométricos, religión, opiniones políticas, orientación sexual) en ningún campo.
