# Autenticación API

> Tokens API personales, tokens de aplicación de la organización para CI y autenticación Bearer.

Las integraciones usan un **token ligado a una organización** enviado como credencial Bearer: un **token API** personal para scripts y clientes de la API, o un **[token de aplicación](#tokens-de-aplicación)** de la organización para enviar resultados desde CI. La aplicación web sigue usando **cookies de sesión** y `X-Organization-Id` para cambiar de organización.

## Tokens API (disponible)

Un token API pertenece al usuario que lo creó, en una organización. Actúa con los permisos que ese usuario tenga en cada momento y se revoca cuando el usuario sale de la organización. Úsalo para scripts y clientes de la API; para enviar resultados de pruebas desde CI, es mejor un [token de aplicación](#tokens-de-aplicación), que pertenece a la organización.

Crear y revocar un token exige el permiso **Administrar API tokens** (`api-tokens.manage`), que tienen los propietarios, los administradores y los miembros. Un **viewer** puede leer la tabla de tokens y nada más: crear o revocar devuelve `403 forbidden`, y la pantalla no le ofrece ningún control de creación ni de revocación. Mientras la organización exija la autenticación en dos pasos, crear un token API también requiere que tu cuenta tenga una aplicación de autenticación confirmada; si no, la petición responde `403 organization_two_factor_required`. Los tokens creados antes siguen funcionando.

1. Inicia sesión en Probara y abre **Tokens de API** desde el menú del avatar (`/user/api-tokens`).
2. Pulsa **Nuevo token** en la cabecera de la pantalla para abrir el diálogo de creación, indica un **nombre** y pulsa **Crear token** — el token se crea para la organización que tiene el foco en ese momento.
3. Copia el secreto del panel de revelación única de inmediato — se muestra **una sola vez** y no se puede recuperar después.
4. Revisa **Tokens activos** en la tabla (Creado, Último uso o **Nunca**) y revoca con el icono de papelera al rotar credenciales.

Formato del token: `probara_` seguido de bytes aleatorios en base64url. Guarda los tokens en el almacén de secretos de tu CI, no en el repositorio.

## Peticiones Bearer

Envía el token en cada llamada protegida a `/api/v1/*` (excepto rutas de auth solo-sesión y gestión de tokens):

```http
Authorization: Bearer probara_xxxxxxxx
```

Con un token API válido **no** necesitas `X-Organization-Id`. El Worker resuelve la organización y la membresía desde el token.

Ejemplo:

```bash
curl -sS -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  "https://app.probara.net/api/v1/projects"
```

## Tokens de aplicación

Un **token de aplicación** pertenece a la organización, no a una persona. Está ligado a una aplicación del catálogo de **Integraciones** — **JUnit XML**, para la [CLI de Probara](https://docs.probara.net/es/developers/cli/), **Playwright**, para el [reporter de Probara para Playwright](https://docs.probara.net/es/guides/integrations/playwright/), **Jest**, para el [reporter de Probara para Jest](https://docs.probara.net/es/guides/integrations/jest/), **Cypress**, para el [reporter de Probara para Cypress](https://docs.probara.net/es/guides/integrations/cypress/), **Vitest**, para el [reporter de Probara para Vitest](https://docs.probara.net/es/guides/integrations/vitest/), o **Cucumber**, para el [reporter de Probara para Cucumber](https://docs.probara.net/es/guides/integrations/cucumber/) y la [sincronización Gherkin](https://docs.probara.net/es/guides/syncing-feature-files-from-git/) de la CLI — y es la credencial que conviene usar en CI:

|                              | Token API                                                                | Token de aplicación                                                         |
| ---------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Pertenece a                  | Un usuario, en una organización                                          | La organización, ligado a una aplicación                                    |
| Se gestiona desde            | **Menú del avatar → Tokens de API**                                      | La tarjeta de la aplicación en **Workspace → Integraciones**                |
| Crear y revocar              | `api-tokens.manage` (lo tienen propietarios, administradores y miembros) | `integrations.manage` (lo tienen propietarios y administradores)            |
| Secreto                      | `probara_…`                                                              | `probara_app_…`                                                             |
| Puede llamar a               | Todo lo que puede su usuario, salvo iniciar una prueba del plan          | Solo las seis llamadas que hace un reporter ([abajo](#llamadas-permitidas)) |
| Cuando su creador se va      | Se revoca                                                                | Sigue funcionando                                                           |
| En la actividad aparece como | Tú, con «Desde un token de API»                                          | La aplicación y el nombre del token, por ejemplo **JUnit XML · nightly-ci** |
| Plan                         | Cualquiera                                                               | Solo planes de pago ([abajo](#plan))                                        |

Un token de aplicación no ocupa ningún puesto. Como sobrevive a quien lo creó, rotarlo depende de ti: revócalo y crea otro cuando pueda haberse filtrado, o cuando alguien que podía leer el secreto deje tu equipo.

### Crear y revocar

1. Como propietario o administrador, abre **Workspace → Integraciones** y selecciona la tarjeta de la aplicación que lo usará: **JUnit XML**, **Playwright**, **Jest**, **Cypress**, **Vitest** o **Cucumber**.
2. Abre la pestaña **Tokens de acceso** y selecciona **Crear un token de aplicación**.
3. Escribe un **Nombre del token** — ponle el nombre de lo que lo usa, como el repositorio o el pipeline — y selecciona **Crear token**.
4. Copia el secreto desde **Copia tu nuevo token**. Empieza por `probara_app_` y se muestra **una sola vez**: guárdalo enseguida como el secreto de CI `PROBARA_API_TOKEN`.

La pestaña **Tokens de acceso** lista los tokens activos con quién creó cada uno, cuándo y cuándo se usó por última vez. **Revocar** (tras una confirmación) lo detiene al momento: la siguiente petición con él responde `401 unauthorized`. Los tokens revocados conservan su nombre y su aplicación en la actividad que dejaron.

La tarjeta usa estas rutas. Como el resto de `/api/v1/orgs/{orgUlid}/integrations`, solo aceptan cookie de sesión; una credencial Bearer se rechaza.

| Método   | Ruta                                                                | Comportamiento                                                                                                                                                                      |
| -------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET`    | `/api/v1/orgs/{orgUlid}/integrations/apps/{app}/tokens`             | `{ items: [{ ulid, app, name, createdAt, lastUsedAt, createdBy }] }` — los tokens activos, nunca el secreto. `createdBy` es `{ ulid, name }`, o `null` cuando esa cuenta se eliminó |
| `POST`   | `/api/v1/orgs/{orgUlid}/integrations/apps/{app}/tokens`             | Cuerpo `{ name }` (1–100 caracteres). `201 { token, appToken }`: `token` es el secreto, que solo se devuelve aquí                                                                   |
| `DELETE` | `/api/v1/orgs/{orgUlid}/integrations/apps/{app}/tokens/{tokenUlid}` | Revoca el token. `204`; `404` si ya está revocado o pertenece a otra organización o app                                                                                             |

`{app}` es `junit`, `playwright`, `jest`, `cypress`, `vitest` o `cucumber`; cualquier otro valor responde `404 not_found`. Cada aplicación lista, crea y revoca solo sus propios tokens. Las seis necesitan el permiso **Administrar integraciones** (`integrations.manage`, que tienen propietarios y administradores); si no, `403 forbidden`. Crear necesita además un plan de pago y está limitado a 10 por usuario por hora.

### Llamadas permitidas

Un token de aplicación puede hacer exactamente las llamadas que hacen la CLI y los reporters de frameworks:

| Método  | Ruta                                                            | Para                                                                                                                                                 |
| ------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST`  | `/api/v1/projects/{projectId}/runs`                             | Crear una ejecución — solo con `automated: true`                                                                                                     |
| `POST`  | `/api/v1/projects/{projectId}/reports`                          | Enviar un [reporte](https://docs.probara.net/es/developers/api-reference/#reportes-de-ci)                                                                                    |
| `POST`  | `/api/v1/projects/{projectId}/gherkin-sync`                     | Sincronizar los escenarios de [archivos Gherkin `.feature`](https://docs.probara.net/es/guides/syncing-feature-files-from-git/)                                              |
| `POST`  | `/api/v1/runs/{runUlid}/results/{resultUlid}/attachments:stage` | Preparar [adjuntos del resultado](https://docs.probara.net/es/developers/api/result-attachments/)                                                                            |
| `PATCH` | `/api/v1/runs/{runUlid}/results/{resultUlid}/attachments`       | Confirmarlos — solo añadir                                                                                                                           |
| `POST`  | `/api/v1/runs/{runUlid}/close`                                  | Cerrar la ejecución                                                                                                                                  |
| `GET`   | `/api/v1/runs/{runUlid}/case-keys`                              | Leer las [claves de casos](https://docs.probara.net/es/developers/api/run-cases/#listar-las-claves-de-casos-de-una-ejecución) de una ejecución, para ejecutar solo sus casos |

Cualquier otro método o ruta — cualquier otra lectura incluida, y el [endpoint MCP](https://docs.probara.net/es/developers/mcp/) `/mcp` — responde `403 forbidden` antes de ejecutar nada. Dentro de esas llamadas:

- Crear una ejecución sin `automated: true` responde `403 forbidden`: un token de aplicación solo crea ejecuciones automatizadas.
- Una confirmación solo puede **añadir** adjuntos. Un cuerpo que deja fuera un adjunto que el resultado ya tiene responde `403 forbidden` y no cambia nada, así que reenvía los archivos confirmados como `{ ulid, position }` junto a los nuevos.
- Actúa con los permisos de ejecutar ejecuciones, escribir casos de prueba y ejecutar resultados, y nada más. No está exento del bloqueo de resultados por asignado de un proyecto (`403 run_case_assignee_locked`) y, como un token API, no se evalúa contra las listas de acceso de los proyectos.
- Sus peticiones cuentan para el límite de tasa de la API de la organización, cada resultado que registra cuenta para el límite mensual de resultados de la API, y `Idempotency-Key` funciona como con cualquier otra credencial.

### Plan

El envío de resultados desde CI está incluido en los planes de pago. En una organización sin plan de pago:

- toda petición con un token de aplicación responde `403 forbidden`, y crear uno se rechaza igual;
- `POST /api/v1/projects/{projectId}/reports`, y `POST /api/v1/projects/{projectId}/runs` con `automated: true`, responden `403 forbidden` con cualquier credencial — tokens API y sesiones incluidos. Crear una ejecución manual sigue disponible;
- listar y revocar tokens de aplicación sigue funcionando.

Pasar al plan Free no revoca los tokens de aplicación: Probara los conserva y los rechaza, y vuelven a funcionar en cuanto la organización vuelve a un plan de pago.

### Atribución

Lo que hace un token de aplicación se atribuye al token, y la aplicación web muestra el logotipo y el nombre de la aplicación junto al nombre del token. En las respuestas de la API, la forma `api_token` del `author` de una ejecución, de un plan de pruebas o de un caso de prueba, del `updatedBy` de un caso de prueba y del `actor` de un evento de auditoría, y el `executedByApiToken` de un resultado, llevan `app` — `"junit"`, `"playwright"`, `"jest"`, `"cypress"`, `"vitest"` o `"cucumber"` para un token de aplicación, `null` para un token API — y los elementos de adjuntos de la organización llevan `uploaderApp` del mismo modo.

## OAuth 2.1 (clientes MCP)

El endpoint `/mcp` también acepta un token de acceso **OAuth 2.1** como credencial Bearer — este es el flujo que usan los clientes MCP interactivos (Claude.ai, Claude Code, Cursor, ChatGPT) en lugar de copiar y pegar un token API. Consulta las [guías de conexión MCP](https://docs.probara.net/es/developers/mcp/) para los pasos de configuración por cliente.

### Descubrimiento

Cualquier cliente puede descubrir el servidor de autorización y los requisitos de este recurso sin configuración previa:

| Documento de metadatos                            | Ruta                                          |
| ------------------------------------------------- | --------------------------------------------- |
| Metadatos del servidor de autorización (RFC 8414) | `GET /.well-known/oauth-authorization-server` |
| Metadatos del recurso protegido (RFC 9728)        | `GET /.well-known/oauth-protected-resource`   |

Ambos documentos anuncian **solo PKCE `S256`** (sin `plain`) y exactamente los scopes de abajo. El documento de metadatos del servidor de autorización también indica `authorization_response_iss_parameter_supported` y `client_id_metadata_document_supported` como `true`.

### Registro dinámico de clientes (DCR)

Los clientes se registran a sí mismos antes de la primera petición de autorización — sin ningún paso manual de "crear una app OAuth":

```http
POST /oauth/register
Content-Type: application/json

{ "redirect_uris": ["https://client.example.com/callback"] }
```

Devuelve un `client_id` y los `redirect_uris` registrados. El registro tiene límite de tasa y un tope máximo. Un registro rechazado devuelve `429` con la cabecera `Retry-After`, en segundos. La expiración del cliente tiene dos niveles: un cliente que nunca completa una autorización expira 24 horas después del registro; un cliente que se autorizó al menos una vez expira 60 días después de su última actividad con un token (intercambio o renovación).

### Documento de metadatos del cliente (CIMD)

Un cliente también puede identificarse con una URL `https` que controla, en lugar de registrarse. Publica un documento JSON en esa URL y usa la URL como `client_id`:

```json
{
  "client_id": "https://app.example.com/oauth/client.json",
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://127.0.0.1:3000/callback"],
  "token_endpoint_auth_method": "none"
}
```

El servidor descarga el documento la primera vez que ve la URL, y de nuevo cuando caduca su caché. La caché dura lo que indique `Cache-Control: max-age` (o `Expires`), entre 5 minutos y 24 horas. Los requisitos:

- El `client_id` del documento es exactamente su URL. La URL usa `https` y tiene una ruta. No tiene query string, fragmento, credenciales, puerto, dirección IP ni host `localhost`, y su nombre de host no termina en punto.
- `client_name` y `redirect_uris` son obligatorios. Un `redirect_uri` de la petición de autorización debe aparecer en la lista.
- `token_endpoint_auth_method` es `none` o está ausente. No se aceptan secretos de cliente.
- El documento es `application/json`, de como máximo 5 KiB, y no puede redirigir.

Las peticiones rechazadas devuelven `429` con la cabecera `Retry-After`, en segundos. Un usuario con sesión puede añadir 10 documentos de cliente nuevos al día, todos los usuarios juntos pueden añadir 20 por dominio registrable al día, y un usuario puede descargar URL desconocidas como máximo 10 veces por host y 30 en total por hora. Un dominio registrable es `example.com` con sus subdominios; bajo un dominio de alojamiento compartido como `github.io`, cada sitio (`alice.github.io`) es un dominio registrable propio.

Cuando el host no responde mientras el servidor descarga o renueva un documento (un tiempo de espera agotado, un error del servidor o un fallo de DNS), un cliente que ya fue autorizado sigue funcionando con su última copia válida hasta 24 horas después de que caducara su caché. Un cliente que nunca fue autorizado, o cuya copia es más antigua que eso, recibe `503` con `error` igual a `temporarily_unavailable` y la cabecera `Retry-After`. Un documento claramente inválido, por ejemplo uno que redirige o indica otro `client_id`, se rechaza de inmediato con `400`, y no se usa ninguna copia en su lugar.

La expiración sigue los dos niveles descritos arriba para los clientes registrados. Un cliente cuyo documento ha expirado se rechaza hasta que la limpieza horaria lo elimine. Después, la misma URL se registra como un cliente nuevo y sus usuarios deben volver a autorizarlo. La pantalla de consentimiento muestra el host que sirvió el documento, para que los usuarios vean de dónde viene la aplicación. El registro mediante `/oauth/register` sigue funcionando para los clientes que lo prefieran.

### Autorización + consentimiento

1. El cliente redirige al usuario a `GET /oauth/authorize` con un `code_challenge` PKCE (`S256`), el `redirect_uri` exacto registrado, el `resource` con la URL del servidor MCP (`https://app.probara.net/mcp`, RFC 8707) y un `scope` **opcional** — consulta [Scopes](#scopes) más abajo para lo que ocurre cuando se omite o no se reconoce. Si se omite `resource`, se toma esa URL. Cualquier otro recurso, o varios, se rechaza con `invalid_target`. Las variantes de escritura de esa misma URL (esquema o host en mayúsculas, barra final) se aceptan.
2. Probara requiere una sesión activa; un visitante no autenticado se envía primero a iniciar sesión y luego vuelve al consentimiento — nunca se emite un código sin sesión.
3. El usuario elige **a qué organización** conceder acceso y aprueba o deniega en la pantalla de consentimiento. La aprobación emite un código de autorización vinculado a esa organización, al scope negociado (posiblemente reducido por el rol) y al desafío PKCE. Mientras la organización exija la autenticación en dos pasos, la aprobación también requiere que la cuenta del usuario tenga una aplicación de autenticación confirmada; si no, el consentimiento responde `403 organization_two_factor_required` y no se emite ningún código.
4. El cliente intercambia el código por tokens:

```http
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=...&redirect_uri=...&code_verifier=...&client_id=...&resource=https%3A%2F%2Fapp.probara.net%2Fmcp
```

Envía el mismo `resource` en esta petición. Si nombra otro recurso, la respuesta es `400` con `error: invalid_target`, y el código sigue sin usar.

Una vez validados `client_id` y `redirect_uri`, cualquier error posterior de autorización (`response_type` no soportado, PKCE inválido o un `resource` que no es la URL MCP de este servidor) se entrega como una redirección `302` al `redirect_uri` registrado con `error`, `error_description` y el `state` reflejado en la query — según RFC 6749 §4.1.2.1 — y no como un error JSON en bruto. Toda redirección al `redirect_uri` registrado, de éxito o de error, incluye además `iss`, con el valor del `issuer` del documento de metadatos (RFC 9207). Verifícalo antes de usar el `code`. Un `client_id` desconocido o un `redirect_uri` no registrado siguen devolviendo un JSON `400` sin redirección (nunca se usa un destino de redirección controlado por un atacante).

### Scopes

| Scope       | Concede                                                                    |
| ----------- | -------------------------------------------------------------------------- |
| `mcp:read`  | Llamadas de solo lectura (`list_*`, `get_*`, etc.)                         |
| `mcp:write` | Llamadas de lectura Y escritura (`create_*`, `update_*`, `delete_*`, etc.) |

Los dos documentos de metadatos listan estos scopes en `scopes_supported`, así un cliente los descubre antes de pedir una concesión.

El `scope` en `/oauth/authorize` es **opcional** (RFC 6749 §3.3) y se negocia de forma flexible, ya que algunos clientes MCP lo omiten o envían valores que este servidor no reconoce:

- **Omitido o vacío** → concede el conjunto completo soportado (`mcp:read mcp:write`).
- **Solo valores no reconocidos** (p. ej. `scope=claudeai`) → también recae en el conjunto completo, igual que el caso omitido.
- **Mezcla de valores conocidos y desconocidos** (p. ej. `scope=mcp:read claudeai`) → concede solo el subconjunto conocido (`mcp:read`).
- **Un subconjunto válido** de los scopes soportados → se concede sin cambios.

El scope negociado es el que se muestra en la pantalla de consentimiento y el que refleja el campo `scope` de la respuesta del token. El scope **efectivo** en el momento de la llamada sigue siendo el mínimo entre el scope negociado de la concesión y el rol **actual** del usuario en esa organización — una degradación de rol después de emitida la concesión aplica de inmediato, sin esperar a que el usuario vuelva a dar su consentimiento.

Cuando una llamada a herramienta necesita un scope que la concesión no tiene, `/mcp` rechaza la llamada antes de ejecutar la herramienta, con `403` y `WWW-Authenticate: Bearer error="insufficient_scope", scope="mcp:write"` (o `scope="mcp:read"`), para que un cliente OAuth pueda volver a pedir ese scope. Cuando la concesión tiene el scope pero tu rol actual no lo permite, la llamada devuelve un error de herramienta `forbidden` que nombra el scope y tu rol, y no se envía ningún desafío `insufficient_scope`. Volver a autorizar no cambia esto: el rol limita lo que puede tener una concesión nueva, así que un propietario o administrador de la organización debe cambiar tu rol. Un token API no tiene scopes y nunca se rechaza así.

### Renovación (refresh)

```http
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=...&client_id=...&resource=https%3A%2F%2Fapp.probara.net%2Fmcp
```

Un `resource` que nombra otro recurso se rechaza con `400` y `error: invalid_target`, y el refresh token sigue utilizable.

Cada renovación **rota** el par de tokens: el refresh token anterior deja de funcionar de inmediato. Reutilizar un refresh token ya rotado revoca toda la familia de tokens como precaución — si esto ocurre, el usuario debe volver a dar su consentimiento.

Volver a dar consentimiento **no** requiere volver a registrar el cliente: mientras el `client_id` siga dentro de su ventana de expiración de consentimiento (ver [Registro dinámico de clientes (DCR)](#registro-dinámico-de-clientes-dcr) arriba), el MISMO `client_id` vuelve a `GET /oauth/authorize` para obtener una nueva concesión. Esa ventana está garantizada para durar más que el tiempo de vida del refresh token, así que un cliente cuyo refresh token acaba de expirar — o cuya familia acaba de ser revocada por detección de reutilización — siempre puede volver a autorizarse sin una nueva llamada de DCR.

### Gestionar y revocar concesiones

Los usuarios gestionan sus propios clientes autorizados en **Workspace → Authorized clients** — revocar ahí invalida de inmediato todos los tokens de acceso y refresh emitidos a ese cliente para esa organización.

## Sesión (solo app web)

| Mecanismo                                                 | Quién lo usa              | Contexto de org                                  |
| --------------------------------------------------------- | ------------------------- | ------------------------------------------------ |
| Cookie de sesión (`tcms_session` / `__Host-tcms_session`) | SPA en navegador          | Cabecera `X-Organization-Id` en rutas de dominio |
| Token API Bearer                                          | Scripts, clientes API     | Desde el token; sin cabecera de org              |
| Token de aplicación Bearer                                | Envío de resultados de CI | Desde el token; sin cabecera de org              |

Rutas solo-sesión (Bearer rechazado): `POST /api/v1/auth/logout`, `logout-all`, `GET /api/v1/auth/me`, `GET`/`PATCH /api/v1/profile`, `POST`/`DELETE /api/v1/profile/avatar`, todo `/api/v1/me/api-tokens`, `PATCH /api/v1/orgs/{orgUlid}` (el cambio de nombre de la organización), y las rutas de tokens de aplicación bajo `/api/v1/orgs/{orgUlid}/integrations`.

## Perfil de usuario (cookie de sesión)

| Método   | Ruta                     | Cuerpo                                                          | Respuesta                                                          |
| -------- | ------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------ |
| `GET`    | `/api/v1/profile`        | —                                                               | `{ firstName, lastName, positionTitle, avatarKey, avatarVersion }` |
| `PATCH`  | `/api/v1/profile`        | `{ firstName, lastName, positionTitle? }`                       | Misma forma que `GET`; `positionTitle` vacío se guarda como `null` |
| `POST`   | `/api/v1/profile/avatar` | `multipart/form-data` con un `file` (PNG/JPEG/WebP, máx. 5 MiB) | `{ avatarKey, avatarVersion }`                                     |
| `DELETE` | `/api/v1/profile/avatar` | —                                                               | `{ avatarKey: null, avatarVersion }`                               |

Las fotos se normalizan en el servidor a WebP 256×256 y se guardan en R2. La lectura pública es `GET /__assets/{avatarKey}?v={avatarVersion}` (sin sesión). `avatarKey` es `null` si no hay foto.

`firstName` y `lastName` son obligatorios y no vacíos en `PATCH`. No hace falta la cabecera `X-Organization-Id`.

## Contrato de signup

`POST /api/v1/auth/signup` exige `firstName` y `lastName` (recortados, no vacíos) además de `email` y `password`. `invitationToken` opcional sin cambios. CREATE y REPAIR persisten los nombres en `users`; `positionTitle` solo se define después con `PATCH /api/v1/profile`.

## Registro de auditoría (solo lectura)

Con un token válido puedes listar los eventos de auditoría de la organización (quién cambió qué) con el mismo Bearer:

`GET /api/v1/orgs/{orgUlid}/audit-events` — consulta [API de eventos de auditoría](https://docs.probara.net/es/developers/api/audit-events/) para paginación por cursor y campos de respuesta. Requiere el permiso de lectura de auditoría (que tienen `owner`/`admin`).

## Errores

| Estado | Código                             | Significado                                                                                                                                                                                                                      |
| ------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | `unauthorized`                     | Cookie o Bearer inválidos                                                                                                                                                                                                        |
| 403    | `forbidden`                        | Un token de aplicación llamó a algo fuera de sus [llamadas permitidas](#llamadas-permitidas), o la organización no tiene un plan de pago para el [envío desde CI](#plan)                                                         |
| 403    | `email_not_verified`               | El usuario todavía no verificó su dirección de correo. Se devuelve al crear tokens de API, aprobar un cliente MCP, invitar miembros, aceptar una invitación por correo, conectar Google o activar la autenticación en dos pasos. |
| 403    | `organization_two_factor_required` | La organización exige la autenticación en dos pasos y tu cuenta no tiene una aplicación de autenticación confirmada. Se devuelve al crear tokens de API o aprobar un cliente MCP para esa organización.                          |
| 429    | `too_many_requests`                | Límite de creación de tokens (10 por usuario por hora, igual para tokens API y tokens de aplicación)                                                                                                                             |

## Límites de tasa

Crear tokens API está limitado a **10 peticiones por usuario por hora**, y crear tokens de aplicación tiene un presupuesto propio del mismo tamaño. `POST /api/v1/auth/signup` aplica dos dimensiones de rate-limit: **5 por hora por IP** y **3 por hora por email**. Las peticiones que excedan cualquiera de los dos límites devuelven `429 too_many_requests`.

`POST /api/v1/auth/login` tiene un límite de **5 por minuto por IP** y **10 por hora por email**. Un navegador que ya inició sesión en esa dirección conserva su propio presupuesto de 10 por hora para ella, así que los intentos fallidos desde otros lugares no pueden bloquear ese navegador. Las peticiones que excedan el límite devuelven `429 too_many_requests`.
