# 02 · Autenticación y multi-tenant

## Las tres credenciales

| Credencial | La usa | Cómo se obtiene | Dónde puede vivir |
| --- | --- | --- | --- |
| **API key** `wak_live_<prefijo>_<secreto>` | El **backend** de una app | La crea administración (o un admin del tenant con `POST /v1/api-keys`) | Solo servidor. **Nunca** en navegador ni en móvil |
| **Token de embebido → access token** | El **iframe** de las pantallas | Vuestro backend pide el token con la API key; el iframe lo canjea | Memoria del iframe (15 min) |
| **Token de administración** | Equipo interno, `/admin/v1` | SSO corporativo (estático solo en desarrollo) | Herramientas internas |

Se envían igual: `Authorization: Bearer <credencial>`.

> La clave completa **se muestra una sola vez** al crearla; el gateway solo guarda su hash. Si se pierde, se revoca y se crea otra. Para rotarla sin corte: crear la nueva, desplegarla, revocar la vieja (`DELETE /v1/api-keys/{id}`).

## Tenant: de quién es cada petición

Una clave puede estar **fijada a un tenant** o ser **multi-tenant**.

| Tipo de clave | Cómo se resuelve el tenant |
| --- | --- |
| Fijada a un tenant | Sale de la clave; las cabeceras de tenant se ignoran |
| Multi-tenant (recomendada para una app que sirve a muchos clientes) | Obligatoria una de estas cabeceras en **cada** petición |

```http
X-Tenant-Ref: CLI-0042        ← el identificador del cliente en VUESTRO sistema (external_ref del tenant)
X-Tenant-Id:  ten_01a1…       ← o el id del gateway
```

Sin ninguna, `400 invalid_request`. Un tenant que no existe o en el que la app no está habilitada responde **igual** (`403 app_not_enabled`) para no revelar qué tenants existen.

**Un recurso de otro tenant responde siempre `404`**, nunca `403`: no se puede averiguar si existe.

## Qué se comprueba en cada llamada (y en qué orden)

1. La clave es válida y no está revocada ni caducada → si no, `401 unauthorized`.
2. El tenant existe y está `active` → `403 license_inactive`.
3. Vuestra app está habilitada para ese tenant → `403 app_not_enabled`.
4. La licencia del módulo está vigente (fechas y estado) → `403 license_inactive`.
5. La función que usa la operación está en la licencia (`chat`, `templates`, `api`, `embed`, `costs`) → `403 feature_not_licensed`.
6. La clave tiene el **alcance** de la operación → `403 forbidden` con `details.required_scope`.
7. El rol alcanza (para sesiones embebidas) → `403 forbidden` con `details.required_role`.
8. No se superó el límite de peticiones → `429 rate_limited`.

El primer fallo corta la petición.

### Licencia suspendida

Con la licencia en `suspended`, **las claves de API pueden seguir leyendo** (`GET`) —para que vuestra app muestre un aviso y no se rompa—, pero no escribir ni enviar. Las pantallas embebidas dejan de funcionar. `expired` lo bloquea todo.

## Alcances (`scopes`)

Cada operación exige un alcance (`messages:send`, `templates:write`…). Dad a cada clave **solo los que necesita**: la clave de vuestro backend de envíos no necesita `keys:write`.

- Tabla completa: [referencia/alcances.md](referencia/alcances.md).
- Una clave no puede crear otra con más alcances que ella misma.

## Límite de peticiones

600 por minuto y clave por defecto (configurable al crearla). Cada respuesta informa:

```http
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 41
```

Al superarlo: `429` con `Retry-After` (segundos). El SDK lo respeta.

> Hoy el contador vive en memoria de cada instancia de la API; con varias réplicas el límite efectivo se multiplica. Se centralizará en Redis (ver [10 · Producción](10-pruebas-y-produccion.md)).

## Idempotencia

Cualquier `POST` acepta `Idempotency-Key` (8–200 caracteres):

- Misma clave y **mismo cuerpo** en 24 h → se devuelve la respuesta original con `Idempotent-Replayed: true`. No se repite el efecto ni se gasta cuota.
- Misma clave con **otro cuerpo** → `409 idempotency_conflict`.
- Generad la clave a partir de **vuestro** hecho de negocio (`cita-48213-recordatorio-1`), no al azar, si queréis que sobreviva a un reinicio de vuestro proceso. Si es aleatoria, guardadla junto al trabajo pendiente y reutilizadla en los reintentos.

## Buenas prácticas de seguridad

- La API key solo en variables de entorno o gestor de secretos del **servidor**.
- Una clave por app y entorno; revocad las que dejen de usarse (`last_used_at` ayuda).
- Verificad siempre la firma de los eventos entrantes ([05](05-recibir-eventos.md)).
- Mapead con cuidado los roles de vuestros usuarios a `agent`/`supervisor`/`admin` ([06](06-ui-embebible.md)): **el rol que enviáis es el que manda**.
