*API REST › Guías*

# OAuth

> Filmo implementa OAuth 2.1 para clientes públicos: código de autorización con PKCE, sin secreto de cliente. El token que emite es una clave de API de tipo OAuth, así que funciona en toda la API REST y en MCP, con los permisos (scopes) que concedió el administrador.

## Clave u OAuth

| Estás construyendo | Uso |
| --- | --- |
| Un proceso de backend o una integración para tu propia empresa que llama a la API REST de forma programada o ante eventos | Una clave de API, creada por un administrador. Consulta Autenticación. |
| Una integración que actúa en nombre de muchas cuentas, o que necesita un consentimiento que el cliente puede revocar | OAuth. El administrador de cada cuenta aprueba la conexión y puede revocarla en cualquier momento. |
| Un cliente MCP remoto, como un conector de asistente, que se conecta a `https://app.filmo.io/api/v1/mcp` | OAuth. El cliente descubre por sí mismo dónde autenticarse. |

OAuth no es un segundo sistema de autenticación. El token de acceso que emite es una clave de API de tipo OAuth: empieza por `fk_live_`, viaja igual, como `Authorization: Bearer`, y funciona en todas las operaciones REST y en MCP. Se aplican los mismos permisos (scopes), el propietario se revalida en cada petición y cuentan los mismos límites de peticiones. Las diferencias son que el token caduca tras 1 hora y que se renueva con un token de refresco. Para trabajo sencillo entre servidores, una clave es más simple y es lo que recomienda Filmo; elige OAuth cuando actúes en nombre de muchas cuentas o necesites un consentimiento revocable. Los pasos de conexión para asistentes están en [Autenticación de MCP](https://docs.filmo.io/es/mcp/authentication).

## Cómo funciona el flujo

El cliente no guarda ningún secreto y demuestra la posesión del código de autorización con PKCE. No hay concesión `client_credentials` ni cliente confidencial: toda conexión necesita el consentimiento de un administrador de la cuenta.

| Paso | Endpoint | Notas |
| --- | --- | --- |
| Descubrimiento | `GET /.well-known/oauth-authorization-server` (RFC 8414) y `GET /.well-known/oauth-protected-resource` (RFC 9728) | Ambos son públicos. El 401 de `/api/v1/mcp` lleva una cabecera `WWW-Authenticate` con `resource_metadata`, de modo que un cliente los encuentra sin configuración. |
| Registro | `POST /api/v1/oauth/register` (RFC 7591) | Abierto, solo JSON. Cliente público: recibes un `client_id` y ningún `client_secret`. |
| Autorización | `GET /oauth/authorize` | PKCE con `S256` es obligatorio. Un administrador de la cuenta ve la pantalla de consentimiento. |
| Token | `POST /api/v1/oauth/token` | `grant_type=authorization_code`, o `grant_type=refresh_token` para rotar. |

## Descubrimiento

Lee los metadatos en lugar de fijar los endpoints a mano. Ambos documentos se sirven bajo `/.well-known/` y se pueden guardar en caché cinco minutos. En producción el emisor, los endpoints y el `resource` protegido están siempre en `https://app.filmo.io`: una petición a `/api/v1/mcp` o a un documento `.well-known` en cualquier otro host de producción se responde con una redirección 308 hacia él.

| Documento | Campos |
| --- | --- |
| `/.well-known/oauth-authorization-server` | `issuer`, `authorization_endpoint` (`/oauth/authorize`), `token_endpoint` (`/api/v1/oauth/token`), `registration_endpoint` (`/api/v1/oauth/register`), `scopes_supported`, `response_types_supported` (`code`), `grant_types_supported` (`authorization_code`, `refresh_token`), `code_challenge_methods_supported` (`S256`), `token_endpoint_auth_methods_supported` (`none`), `service_documentation` |
| `/.well-known/oauth-protected-resource` | `resource` (el endpoint MCP, `https://app.filmo.io/api/v1/mcp`), `authorization_servers`, `scopes_supported`, `bearer_methods_supported` (`header`), `resource_documentation` |

Los metadatos no tienen `revocation_endpoint`, ni endpoint de introspección, ni `response_modes_supported`. Los endpoints de OAuth y MCP no envían cabeceras CORS, así que llama a estos endpoints desde un servidor, no desde código de navegador.

## Registrar un cliente

El registro es abierto: no necesita credencial y solo acepta JSON. El cliente es público, así que no hay `client_secret` ni token de acceso al registro.

- `redirect_uris` es obligatorio, con 10 entradas como máximo. Cada una es un URI absoluto sin fragmento, y es `https`, o `http` en un host de loopback como `127.0.0.1` o `[::1]` (cualquier puerto y ruta), o un esquema personalizado de una app nativa.
- `client_name` es opcional, y se rechaza un nombre de más de 200 caracteres. El resto de metadatos que envíes (`grant_types`, `response_types`, `token_endpoint_auth_method`, `scope`, `logo_uri`, `software_id`) se ignora: el cliente es siempre un cliente público con `token_endpoint_auth_method` establecido en `none`.
- La respuesta es 201 con `client_id`, `client_id_issued_at`, `client_name`, `redirect_uris`, `grant_types`, `response_types`, `token_endpoint_auth_method` y `token_endpoint`.
- Un cliente que nunca se usa puede purgarse a los 30 días, así que regístralo una vez y conserva el `client_id`.

*Registrar un cliente*

```bash
# Register a public client once (no credentials needed). JSON only.
curl -sS -X POST "${FILMO_BASE_URL:-https://app.filmo.io}/api/v1/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{"client_name": "Acme Sync", "redirect_uris": ["https://example.com/oauth/callback"]}'
```

## Autorizar

Envía el navegador del administrador a `/oauth/authorize` en `https://app.filmo.io`. Lee estos parámetros e ignora cualquier otro.

| Parámetro | Regla |
| --- | --- |
| `client_id` | Obligatorio. El cliente registrado. |
| `redirect_uri` | Obligatorio. Debe ser exactamente igual a una de las URI que registraste: sin comodines y sin puerto flexible para loopback. |
| `response_type` | Debe ser `code`. |
| `scope` | Opcional. Permisos separados por espacios que se preseleccionan en la pantalla de consentimiento. Los scopes desconocidos se ignoran. |
| `code_challenge` | Required. |
| `code_challenge_method` | Solo `S256`; `plain` se rechaza. |
| `state` | Opcional, se devuelve sin cambios a tu `redirect_uri`. |

Los errores que ocurren antes de que Filmo pueda fiarse del `redirect_uri` (un cliente desconocido o un `redirect_uri` que no coincide) se muestran en la página de Filmo y no se redirigen. Cualquier error posterior se envía a tu `redirect_uri` como `error`, `error_description` y `state`. El código de autorización se entrega siempre en la query string, nunca en un fragmento.

El código es válido durante 60 segundos y se puede usar una vez. Su `code_verifier` debe tener entre 43 y 128 caracteres.

## Consentimiento

Solo un administrador puede conceder una conexión. La pantalla de consentimiento permite al administrador elegir la suscripción que usará la conexión y los permisos (scopes) que concede: concede el mínimo que necesita la integración. Solo vienen preseleccionados los permisos de lectura: los que pide el cliente, o todos los de lectura si no pide ninguno. Los permisos de escritura, [`workflows:run`](https://docs.filmo.io/es/rest-api/guides/scopes#workflows:run) y [`ai:ask`](https://docs.filmo.io/es/rest-api/guides/scopes#ai:ask) empiezan siempre sin seleccionar, aunque el cliente los pida; los que pidió llevan la etiqueta *Lo pide la integración*, y el administrador los activa a mano. El token queda vinculado a esa suscripción, y todo lo que escribe la conexión se atribuye a ese administrador, igual que con una clave. La conexión queda registrada en el registro de actividad de la cuenta.

Aprueba una conexión solo si la iniciaste tú, y comprueba el nombre del cliente y el host al que redirige. Una conexión a la que se concedieron menos scopes de los que pidió funciona con los que tiene: lee `scope` en la respuesta del token.

## Obtener y renovar tokens

Haz POST a `/api/v1/oauth/token` como formulario (`application/x-www-form-urlencoded`) o como JSON. El cliente es público: envía `client_id` y ningún secreto.

- `grant_type=authorization_code` requiere `redirect_uri`, `code` y `code_verifier`.
- `grant_type=refresh_token` requiere `refresh_token`.
- Cualquier otro tipo de grant se rechaza con `unsupported_grant_type`. No existe `client_credentials`.
- `scope` y `resource` en la petición del token se ignoran: los permisos son los concedidos en la pantalla de consentimiento.

*Intercambiar un código y renovar*

```bash
# Exchange the authorization code (all values come from your own flow).
curl -sS -X POST "${FILMO_BASE_URL:-https://app.filmo.io}/api/v1/oauth/token" \
  -d grant_type=authorization_code \
  -d client_id="$FILMO_OAUTH_CLIENT_ID" \
  -d redirect_uri="https://example.com/oauth/callback" \
  -d code="$AUTH_CODE" \
  -d code_verifier="$CODE_VERIFIER"

# Later, rotate: send the latest refresh token and keep the new pair.
curl -sS -X POST "${FILMO_BASE_URL:-https://app.filmo.io}/api/v1/oauth/token" \
  -d grant_type=refresh_token \
  -d client_id="$FILMO_OAUTH_CLIENT_ID" \
  -d refresh_token="$REFRESH_TOKEN"
```

La respuesta es 200 con `Cache-Control: no-store`:

```json
{
  "access_token": "<the access token, starts with fk_live_>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "<the refresh token>",
  "scope": "<the scopes actually granted, space-separated>"
}
```

### Duración y rotación

- El token de acceso caduca tras 1 hora. Después, las llamadas responden 401 [`api_key_expired`](https://docs.filmo.io/es/rest-api/errors/api_key_expired) hasta que lo refresques.
- El token de refresco dura 30 días, contados desde la última rotación. Cada refresco emite uno nuevo, así que una conexión que se refresca al menos cada 30 días sigue activa.
- La rotación reescribe la misma conexión: el access token anterior deja de funcionar al instante, y la conexión sigue siendo una única entrada en la sección API y MCP de los ajustes.
- Si envías un refresh token que ya se rotó, dentro de 10 segundos y desde el mismo cliente, recibes de nuevo el mismo par nuevo, lo que te protege de un reintento tras una respuesta perdida. Más tarde, la reutilización se trata como una fuga: se revoca toda la cadena y recibes `invalid_grant`.
- Guarda el refresh token más reciente y deja que solo un worker renueve cada vez.
- Antes de rotar, Filmo comprueba que el administrador que dio su consentimiento sigue siendo un administrador activo. Si no, la renovación falla con `invalid_grant`.

## Usar el token

Envíalo como `Authorization: Bearer` a cualquier operación REST o al endpoint MCP. Los scopes son los que lista la respuesta del token, y el propietario de la clave se comprueba en cada petición, igual que con una clave: si el administrador que dio el consentimiento se va o deja de ser administrador, las llamadas fallan con 401 [`api_key_owner_inactive`](https://docs.filmo.io/es/rest-api/errors/api_key_owner_inactive) o [`api_key_owner_not_admin`](https://docs.filmo.io/es/rest-api/errors/api_key_owner_not_admin). Un 401 de la API REST no lleva cabecera `WWW-Authenticate`; solo la lleva el 401 del endpoint MCP, que nombra los scopes de lectura en `scope`, de modo que un cliente pide acceso de lectura por defecto.

Los indicadores de recurso de RFC 8707 no están soportados. Un parámetro `resource` en la petición de autorización o de token se ignora, y el token no queda vinculado a una audiencia.

## Revocar una conexión

No hay endpoint de revocación (RFC 7009). El administrador revoca la conexión en la sección API y MCP de los ajustes (*Ajustes → API y MCP*), donde aparece etiquetada como integración conectada. El acceso termina de inmediato, y a partir de entonces un refresco falla con `invalid_grant`, así que el usuario tiene que volver a dar su consentimiento. Consulta [Autenticación](https://docs.filmo.io/es/rest-api/guides/authentication#revoke). Las conexiones cuentan en el total de 50 claves no revocadas por suscripción, pero solo se rechaza en el límite la creación manual de una clave.

## Errores de OAuth

Los endpoints de OAuth no usan el sobre del resto de la API ni responden en `application/problem+json`. Responden en el formato plano de OAuth 2.1, que es lo que esperan los clientes estándar. El `error_description` está siempre en español: ramifica según `error`, nunca según el texto.

```json
{ "error": "invalid_grant", "error_description": "..." }
```

| Estado | `error` |
| --- | --- |
| 400 | `invalid_request`, `invalid_grant`, `unsupported_grant_type`, `invalid_scope`, `invalid_redirect_uri`, `invalid_client_metadata` |
| 401 | `invalid_client` |
| 403 | `unauthorized_client`, `access_denied` |
| 500 | `server_error` |

Los endpoints de OAuth no devuelven `X-Request-Id`. Un 401 `invalid_client` no tiene cabecera `WWW-Authenticate`.

## Límites de peticiones de OAuth

Los endpoints de OAuth tienen sus propios límites, contados por dirección IP en una ventana de un minuto: 5 peticiones por minuto en `/api/v1/oauth/register` y 120 por minuto en `/api/v1/oauth/token`. Al superar el límite recibes 429 con `Retry-After`. Gestiona cualquier 429 de estos endpoints esperando `Retry-After`, no leyendo el cuerpo: el cuerpo no es igual en todos los casos. Estas respuestas no llevan cabeceras `RateLimit-*`. Los límites de REST se describen en [Límites de peticiones](https://docs.filmo.io/es/rest-api/guides/rate-limits).

## Antes de depender de ello

Usa el soporte de OAuth de tu cliente MCP o una biblioteca estándar de OAuth 2.1 en lugar de peticiones construidas a mano. Para integraciones de servidor a servidor que actúan sobre una cuenta, crea una clave de API en su lugar.

---

URL canónica: https://docs.filmo.io/es/rest-api/guides/oauth  
Última actualización: 2026-10-09