# Crear una aplicación OAuth

> Permite que los usuarios conecten tu aplicación a su espacio de trabajo de Emailit con OAuth 2.1 y PKCE, con el registro del cliente, la actualización de tokens, la revocación y los errores.

Emailit ejecuta un servidor de autorización OAuth 2.1 en `https://api.emailit.com`. Úsalo cuando crees una integración, como un CRM, una herramienta no-code o un cliente de IA, que actúe en nombre de usuarios de Emailit. Tus usuarios inician sesión y aprueban el acceso en el navegador, y tú obtienes tokens para los espacios de trabajo que elijan sin manejar nunca sus claves de API. Es el mismo flujo que usa el [servidor MCP](/es/docs/mcp/).

## Cómo funciona

1. **Registra un cliente** con el registro dinámico de clientes o aloja un documento de metadatos del ID de cliente.

2. **Envía al usuario a la URL de autorización** con un desafío de código PKCE.

3. **El usuario inicia sesión en Emailit**, elige los espacios de trabajo que puede usar tu aplicación y aprueba el permiso solicitado.

4. **Emailit redirige de vuelta** a tu URI de redirección con un código de autorización de un solo uso.

5. **Canjea el código** por un token de acceso de 15 minutos y un token de actualización.

6. **Llama a la API** con el token de acceso y actualízalo cuando caduque.

## Endpoints

| Método | Ruta | Finalidad |
| --- | --- | --- |
| `GET` | `/.well-known/oauth-authorization-server` | Metadatos del servidor de autorización (RFC 8414) |
| `GET` | `/.well-known/oauth-protected-resource` | Metadatos del recurso protegido para la API REST |
| `GET` | `/.well-known/oauth-protected-resource/mcp` | Metadatos del recurso protegido para el servidor MCP |
| `POST` | `/oauth/register` | Registro dinámico de clientes (RFC 7591) |
| `GET` | `/oauth/authorize` | Inicio de sesión y consentimiento en el navegador |
| `POST` | `/oauth/token` | Canjear un código o un token de actualización |
| `POST` | `/oauth/revoke` | Revocar una autorización con su token de actualización (RFC 7009) |
| `GET` | `/oauth/grants` | Listar autorizaciones (las del usuario que ha iniciado sesión, o las de un espacio de trabajo con una clave de API) |
| `POST` | `/oauth/grants/:id/revoke` | Revocar una autorización |
| `PUT` | `/oauth/grants/:id/workspaces` | Cambiar los espacios de trabajo que puede usar una autorización (el usuario que conectó la aplicación) |

Todas las rutas están en `https://api.emailit.com`.

## Descubrir el servidor

```bash
curl https://api.emailit.com/.well-known/oauth-authorization-server
```

```json
{
  "issuer": "https://api.emailit.com",
  "authorization_endpoint": "https://api.emailit.com/oauth/authorize",
  "token_endpoint": "https://api.emailit.com/oauth/token",
  "registration_endpoint": "https://api.emailit.com/oauth/register",
  "revocation_endpoint": "https://api.emailit.com/oauth/revoke",
  "jwks_uri": "https://api.emailit.com/.well-known/jwks.json",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true,
  "scopes_supported": ["sending", "full"]
}
```

Los clientes MCP empiezan, en cambio, por los metadatos del recurso protegido. Una petición sin autenticar a `https://api.emailit.com/mcp` devuelve `401` con `WWW-Authenticate: Bearer resource_metadata="https://api.emailit.com/.well-known/oauth-protected-resource/mcp"`, y ese documento apunta al servidor de autorización:

```json
{
  "resource": "https://api.emailit.com/mcp",
  "authorization_servers": ["https://api.emailit.com"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["sending", "full"]
}
```

## Permisos

| Permiso | Concede |
| --- | --- |
| `sending` | Enviar y reenviar emails, y reprogramar, cancelar y reintentar envíos. El mismo acceso que una clave de API Sending Only. |
| `full` | Todos los endpoints de la API REST y todas las herramientas MCP. El mismo acceso que una clave de API Full Access. |

`full` ya incluye todo lo que permite `sending`. Solicita `sending` si tu aplicación solo envía y `full` en los demás casos; también se acepta `sending full`, separado por un espacio. Si omites `scope`, Emailit usa todos los permisos que registró el cliente.

## Registrar tu cliente

Puedes registrar un cliente de dos formas. Ambas funcionan con todos los flujos de esta página.

### Registro dinámico de clientes

Envía una petición de registro. No hace falta autenticación, y cada dirección IP puede registrar hasta 20 clientes por hora.

```bash
curl https://api.emailit.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Acme CRM",
    "redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "scope": "full",
    "token_endpoint_auth_method": "client_secret_basic",
    "client_uri": "https://crm.acme.com",
    "logo_uri": "https://crm.acme.com/logo.png"
  }'
```

| Campo | Obligatorio | Descripción |
| --- | --- | --- |
| `client_name` | Sí | Se muestra en la página de consentimiento. Hasta 200 caracteres. |
| `redirect_uris` | Sí | De 1 a 10 URI de redirección. Consulta [Reglas de las URI de redirección](#redirect-uri-rules). |
| `grant_types` | No | Debe incluir `authorization_code`; puede incluir `refresh_token`. Por defecto, ambos. |
| `response_types` | No | Solo `code`. |
| `scope` | No | Permisos separados por espacios. Por defecto, `sending full`. |
| `token_endpoint_auth_method` | No | `none` (por defecto) para clientes públicos, como las aplicaciones de escritorio, móviles y de navegador; `client_secret_basic` o `client_secret_post` para las aplicaciones de servidor. |
| `client_uri` | No | La página de inicio de tu aplicación. |
| `logo_uri` | No | El logotipo que se muestra en la página de consentimiento. |

La respuesta `201` repite los metadatos y añade un `client_id`. Los clientes confidenciales también reciben un `client_secret`, que se muestra una sola vez:

```json
{
  "client_id": "3e8a1f52-7c4b-4d29-8e6f-1a5b9c0d2e73",
  "client_id_issued_at": 1790847000,
  "client_name": "Acme CRM",
  "redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "full",
  "token_endpoint_auth_method": "client_secret_basic",
  "client_uri": "https://crm.acme.com",
  "logo_uri": "https://crm.acme.com/logo.png",
  "client_secret": "Ky7WI2z5HxoT6q1z19tuiIev_U1zX0_FKhIIfh0OBco",
  "client_secret_expires_at": 0
}
```

`client_secret_expires_at` siempre es `0`: los secretos no caducan. Todos los clientes, públicos o confidenciales, deben usar PKCE.

### Documento de metadatos del ID de cliente

Si tu aplicación puede alojar un archivo JSON estático, puedes saltarte el registro. Publica un documento en una URL HTTPS y usa esa URL como `client_id`:

```json title="https://crm.acme.com/oauth/client.json"
{
  "client_id": "https://crm.acme.com/oauth/client.json",
  "client_name": "Acme CRM",
  "client_uri": "https://crm.acme.com",
  "logo_uri": "https://crm.acme.com/logo.png",
  "redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none",
  "scope": "full"
}
```

- El `client_id` del documento debe ser exactamente igual a la URL del documento.
- `token_endpoint_auth_method` debe ser `none`. Son clientes públicos que dependen de PKCE.
- Las URI de redirección HTTPS deben estar en el mismo host que el documento.
- Emailit descarga el documento durante la autorización, con un tiempo de espera de 5 segundos y sin seguir redirecciones, y lo guarda en caché durante 5 minutos.
- La página de consentimiento muestra el host del documento, por ejemplo `crm.acme.com`, en lugar de `client_name`.

Los clientes de IA usan este método: ChatGPT, Claude y Grok se identifican con sus propios documentos de metadatos del ID de cliente, así que se conectan sin registrarse antes. Para Grok, Emailit también acepta redirecciones a `console.x.ai`.

### Reglas de las URI de redirección

- Se permiten las URI `https://`.
- `http://` solo se permite para hosts de loopback: `127.0.0.1`, `localhost` y `[::1]`. En las URI de loopback, el puerto puede ser distinto en el momento de la autorización, pero el host, la ruta y la consulta deben coincidir. `localhost` y `127.0.0.1` son hosts distintos.
- Los esquemas de uso privado, como `cursor://`, `vscode://` o `com.acme.crm://`, se permiten para las aplicaciones nativas.
- Se rechazan las URI `file`, `ftp`, `data`, `javascript`, `blob`, `about` y `vbscript`, y cualquier URI con un `#fragment`.
- Salvo los puertos de loopback, la `redirect_uri` que envíes debe coincidir exactamente con una registrada.

## Enviar al usuario a Emailit

Crea un verificador y un desafío PKCE, y un `state` aleatorio, para cada autorización:

```javascript

const codeVerifier = randomBytes(32).toString('base64url');
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
const state = randomBytes(16).toString('base64url');
// Store codeVerifier and state in the user's session.
```

Después, redirige el navegador del usuario al endpoint de autorización:

```text
https://api.emailit.com/oauth/authorize
  ?response_type=code
  &client_id=3e8a1f52-7c4b-4d29-8e6f-1a5b9c0d2e73
  &redirect_uri=https%3A%2F%2Fcrm.acme.com%2Foauth%2Femailit%2Fcallback
  &scope=full
  &state=Jq3k9V0n2xR7bLm1
  &code_challenge=zsrvXVr2pbLDHbccAx_NY9osQ6nwqTnDZlIeFWKQOsg
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fapi.emailit.com
```

| Parámetro | Obligatorio | Descripción |
| --- | --- | --- |
| `response_type` | Sí | `code` |
| `client_id` | Sí | Tu ID de cliente o la URL de tu documento de metadatos. |
| `redirect_uri` | Sí | Una de tus URI de redirección registradas. |
| `code_challenge` | Sí | El SHA-256 del verificador de código, codificado en Base64url. |
| `code_challenge_method` | Recomendado | `S256`. `plain` no se admite. |
| `scope` | Recomendado | `sending` o `full`. Debe ser un permiso que haya registrado el cliente. |
| `state` | Recomendado | Un valor aleatorio que compruebas en el callback. |
| `resource` | No | El recurso al que quieres llamar (RFC 8707): `https://api.emailit.com` para la API REST o `https://api.emailit.com/mcp` para MCP. Los tokens funcionan en ambos de todos modos. |

Abre esta URL en el navegador del usuario. No la solicites desde tu backend.

## Qué ve el usuario

1. **Inicia sesión en Emailit.** El usuario introduce su email y su contraseña. Si usa una app de autenticación para la autenticación en dos pasos, a continuación introduce su código o un código de recuperación.
2. **Elige los espacios de trabajo.** La página muestra tu logotipo, el nombre de tu cliente (o el host de tu documento de metadatos) y los permisos que solicitaste. El usuario elige **All my workspaces**, que incluye los espacios de trabajo que cree o a los que se una más adelante, o **Only these workspaces** con los que marque, y elige en cuál empieza tu aplicación.
3. **Permite el acceso.** Selecciona **Allow access** o **Deny**.

Una autorización puede cubrir varios espacios de trabajo. El usuario puede cambiar la lista más adelante en **Account > Connected apps** en el panel, y tu aplicación ve el cambio en su siguiente petición.

Al aprobar, Emailit redirige a tu URI de redirección:

```text
https://crm.acme.com/oauth/emailit/callback?code=XvRKyG5Pkplrd3xkRNaEayKkpkebEMdF&state=Jq3k9V0n2xR7bLm1&iss=https%3A%2F%2Fapi.emailit.com
```

Comprueba que `state` coincide con el valor que guardaste y que `iss` es `https://api.emailit.com`. El código es válido durante 10 minutos y solo se puede usar una vez. Si el usuario selecciona **Deny**, la redirección lleva en su lugar `error=access_denied`.

## Canjear el código por tokens

Envía un POST al endpoint de tokens como `application/x-www-form-urlencoded` (también se acepta JSON). Envía la misma `redirect_uri` y el verificador de código original:

**Cliente confidencial**

```bash
curl https://api.emailit.com/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://crm.acme.com/oauth/emailit/callback \
  -d code_verifier="$CODE_VERIFIER"
```

  Con `client_secret_basic`, envía el ID y el secreto del cliente en la cabecera `Authorization: Basic`. Con `client_secret_post`, envía en su lugar `client_id` y `client_secret` como campos del formulario. No envíes el secreto de las dos formas.

**Cliente público**

```bash
curl https://api.emailit.com/oauth/token \
  -d grant_type=authorization_code \
  -d client_id="$CLIENT_ID" \
  -d code="$CODE" \
  -d redirect_uri=http://127.0.0.1:53682/callback \
  -d code_verifier="$CODE_VERIFIER"
```

  Los clientes públicos envían `client_id` y ningún secreto. PKCE demuestra que el flujo lo inició el mismo cliente.

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9hdXRoLWhzMjU2In0…",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "XvRKyG5Pkplrd3xkRNaEayKkpkebEMdFn0h1VIuqixAOXybMtJfK8w",
  "scope": "full"
}
```

El token de acceso dura 15 minutos (`expires_in` está en segundos). Trátalo como una cadena opaca: no lo analices ni dependas de su contenido.

## Llamar a la API

Usa el token de acceso como token bearer en la API REST y en el servidor MCP, exactamente igual que una clave de API:

```bash
curl https://api.emailit.com/v2/domains \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Las peticiones REST se ejecutan en el espacio de trabajo por defecto de la autorización: el que el usuario eligió para empezar o el que haya elegido después. Las herramientas MCP también pueden actuar sobre otro espacio de trabajo permitido con su argumento `workspace`. Cada petición usa el permiso concedido y el rol del usuario en el espacio de trabajo, así que una petición fuera del permiso, o una ruta reservada a administradores a la que llama un miembro del espacio de trabajo, devuelve `403`. Si el usuario abandona un espacio de trabajo, la autorización también lo pierde.

## Actualizar los tokens

Antes de que caduque el token de acceso, o cuando una petición devuelva `401`, obtén uno nuevo:

```bash
curl https://api.emailit.com/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN"
```

- **Rotación.** Cada actualización devuelve un `refresh_token` nuevo, válido durante 60 días. Guárdalo y descarta el anterior. Mientras actualices en un plazo de 60 días, la conexión no caduca.
- **Detección de reutilización.** Si un token de actualización antiguo se vuelve a usar más de un minuto después de que se sustituyera, Emailit devuelve `invalid_grant` (`Refresh token reuse detected`) y revoca toda la autorización. Entonces el usuario tiene que volver a autorizar. Serializa las actualizaciones para que dos workers nunca usen el mismo token de actualización.
- **Permiso más limitado.** Puedes pasar `scope` para obtener un token de acceso con menos permisos que la autorización. No puedes pedir más.

## Revocar una autorización

Cuando un usuario desconecte Emailit de tu aplicación, revoca la autorización con su token de actualización:

```bash
curl https://api.emailit.com/oauth/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d token="$REFRESH_TOKEN"
```

El endpoint autentica a tu cliente igual que el endpoint de tokens y siempre devuelve `200` con el cuerpo vacío. Revocar el token de actualización revoca toda la autorización: sus tokens de acceso dejan de funcionar en la siguiente petición. Los tokens de acceso no se pueden revocar por separado; `token_type_hint=access_token` devuelve `invalid_request`.

Los usuarios también pueden revocar el acceso de tu aplicación desde su lado, en [Connected apps](/es/docs/account/connected-apps/) en el panel, o con la API de autorizaciones que se describe a continuación.

## Gestionar las autorizaciones

La API de autorizaciones lista y modifica las autorizaciones desde el lado del usuario. Acepta la sesión del panel del usuario o una clave de API Full Access:

| Quién llama | `GET /oauth/grants` | `POST /oauth/grants/:id/revoke` | `PUT /oauth/grants/:id/workspaces` |
| --- | --- | --- | --- |
| El usuario que conectó la aplicación | Sus autorizaciones en todos los espacios de trabajo | Revoca toda la autorización | Cambia los espacios de trabajo de la autorización |
| Clave de API Full Access | Las autorizaciones que incluyen el espacio de trabajo de la clave | Quita el espacio de trabajo de la clave de la autorización; la autorización se revoca cuando no le queda ningún espacio de trabajo | No permitido (`403`) |

Cada autorización de la lista tiene esta forma:

```json
{
  "id": "9b1f6c2e-4d3a-4f8e-9a7b-2c5d8e1f0a3b",
  "client_id": "https://chatgpt.com/oauth/client.json",
  "client": { "name": "ChatGPT", "uri": "https://chatgpt.com", "logo_uri": null },
  "default_workspace": { "id": "w3f9a1c2e", "name": "Acme" },
  "workspace_access": "selected",
  "workspaces": [{ "id": "w3f9a1c2e", "name": "Acme" }],
  "scopes": ["sending", "full"],
  "resource": "https://api.emailit.com/mcp",
  "created_at": "2026-10-01T09:30:12Z",
  "revoked_at": null,
  "revoked_reason": null
}
```

Para cambiar los espacios de trabajo de una autorización, envía `access` (`all` o `selected`), `workspace_ids` para `selected` y un `default_workspace_id` opcional que debe ser uno de los espacios de trabajo permitidos:

```bash
curl -X PUT https://api.emailit.com/oauth/grants/9b1f6c2e-4d3a-4f8e-9a7b-2c5d8e1f0a3b/workspaces \
  -H "Authorization: Bearer $DASHBOARD_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "access": "selected", "workspace_ids": ["w3f9a1c2e"], "default_workspace_id": "w3f9a1c2e" }'
```

El usuario debe ser miembro de todos los espacios de trabajo que seleccione. Una autorización revocada no se puede editar (`409`); la aplicación tiene que volver a conectarse.

## Errores

Los endpoints OAuth devuelven los errores en el formato OAuth, no en el [formato de errores de la API REST](/es/docs/api-reference/errors/):

```json
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
```

| Error | Código | Cuándo |
| --- | --- | --- |
| `invalid_request` | 400 | Falta un parámetro o no es válido, el cliente es desconocido en la autorización, la `redirect_uri` no está registrada, `code_challenge_method` no es `S256` o se envió un secreto de cliente tanto en la cabecera como en el cuerpo. |
| `invalid_client` | 401 | El endpoint de tokens o de revocación no puede autenticar al cliente: cliente desconocido, o secreto ausente o incorrecto. |
| `invalid_grant` | 400 | El código no es válido, ya se usó o ha caducado; la `redirect_uri` o el verificador PKCE no coinciden; o el token de actualización no es válido, ha caducado, se revocó o se reutilizó. |
| `invalid_scope` | 400 | El permiso no se admite, no se registró para el cliente o, al actualizar, supera el de la autorización. |
| `unsupported_response_type` | 400 | `response_type` no es `code`. |
| `unsupported_grant_type` | 400 | `grant_type` no es `authorization_code` ni `refresh_token`. |
| `access_denied` | Redirección | El usuario seleccionó **Deny**. |
| `too_many_requests` | 429 | Más de 20 registros por hora desde una misma dirección IP. |
| `server_error` | 500 | Algo ha fallado en Emailit. Vuelve a intentarlo. |

Hasta que se validan el `client_id` y la `redirect_uri`, los errores de autorización se muestran como JSON en el navegador y nunca se redirigen. Después, los errores se redirigen a tu callback (con `error`, `error_description`, `state` e `iss`) solo en las URI de redirección de loopback y de uso privado y en los clientes con documento de metadatos; los demás clientes reciben una página de error JSON. El **Deny** de un usuario siempre se redirige.

## Lista de comprobación de seguridad

- Guarda `client_secret` y los tokens de actualización en tu servidor, cifrados en reposo. No incluyas nunca un secreto de cliente en una aplicación móvil, de escritorio o de navegador; usa en su lugar un cliente público con PKCE.
- Genera un `state` y un verificador de código nuevos para cada autorización, y comprueba `state` e `iss` en el callback.
- Registra URI de redirección exactas. No uses redirectores abiertos como callbacks.
- Guarda cada autorización con el espacio de trabajo al que pertenece y gestiona `invalid_grant` pidiendo al usuario que vuelva a conectarse.
- Solicita `sending` si solo envías emails.

## Ver también

  - [Espacios de trabajo y permisos](/es/docs/mcp/workspaces-and-permissions/): Autorizaciones, duración de los tokens y revocación.
  - [Referencia de la API](/es/docs/api-reference/): Los endpoints a los que pueden llamar tus tokens.
  - [Claves de API](/es/docs/developers/api-keys/): Para tus propios scripts, las claves son más sencillas.
  - [Servidor MCP](/es/docs/mcp/): El flujo OAuth en la práctica.

---
Fuente: https://emailit.com/es/docs/developers/oauth-apps/
