Ir al contenido

Autenticación API

Las integraciones usan un token API ligado a una organización enviado como credencial Bearer. La aplicación web sigue usando cookies de sesión y X-Organization-Id para cambiar de organización.

  1. Inicia sesión en Probara y abre Workspace → API tokens (/workspace/api-tokens).
  2. En Nuevo token, indica un nombre, elige la organización (selector compartido, limitado a tus membresías) y pulsa Crear token.
  3. Copia el secreto del panel ámbar 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.

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

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:

Ventana de terminal
curl -sS -H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/projects"

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 para los pasos de configuración por cliente.

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

Documento de metadatosRuta
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.

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”:

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. 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).

  1. El cliente redirige al usuario a GET /oauth/authorize con un code_challenge PKCE (S256), el redirect_uri exacto registrado y un scope opcional — consulta Scopes más abajo para lo que ocurre cuando se omite o no se reconoce.
  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.
  4. El cliente intercambia el código por tokens:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=...&redirect_uri=...&code_verifier=...&client_id=...

Una vez validados client_id y redirect_uri, cualquier error posterior de autorización (response_type no soportado, PKCE inválido) 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. 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).

ScopeConcede
mcp:readLlamadas de solo lectura (list_*, get_*, etc.)
mcp:writeLlamadas de lectura Y escritura (create_*, update_*, delete_*, etc.)

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.

POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=...&client_id=...

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) 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.

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.

MecanismoQuién lo usaContexto de org
Cookie de sesión (tcms_session / __Host-tcms_session)SPA en navegadorCabecera X-Organization-Id en rutas de dominio
Token API BearerCI, scripts, clientes APIDesde 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, y todo /api/v1/me/api-tokens.

MétodoRutaCuerpoRespuesta
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/avatarmultipart/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.

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.

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 para paginación por cursor y campos de respuesta. Requiere rol owner o admin en esa organización.

EstadoCódigoSignificado
401unauthorizedCookie o Bearer inválidos
403email_not_verifiedResidual: devuelto para usuarios legados con email_verified_at en NULL (backups, edición manual). Los signups nuevos quedan verificados automáticamente y no disparan este código.
429too_many_requestsLímite de creación de tokens (10 por usuario por hora)
500email_send_failedDevuelto por request-password-reset (y por la rama COLLISION de signup) cuando el proveedor de email rechaza el envío. El usuario puede reintentar.

Crear tokens API está limitado a 10 peticiones por usuario por hora. 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.