# Créer une application OAuth

> Permettez aux utilisateurs de connecter votre application à leur espace de travail Emailit avec OAuth 2.1 et PKCE : enregistrement du client, actualisation des jetons, révocation et erreurs.

Emailit exploite un serveur d’autorisation OAuth 2.1 à l’adresse `https://api.emailit.com`. Utilisez-le quand vous créez une intégration (un CRM, un outil no-code ou un client IA, par exemple) qui agit au nom d’utilisateurs Emailit. Vos utilisateurs se connectent et approuvent l’accès dans le navigateur, et vous obtenez des jetons pour les espaces de travail qu’ils choisissent sans jamais manipuler leurs clés API. C’est le même flux que celui du [serveur MCP](/fr/docs/mcp/).

## Fonctionnement

1. **Enregistrez un client** avec l’enregistrement dynamique de client, ou hébergez un document de métadonnées d’ID client.

2. **Envoyez l’utilisateur vers l’URL d’autorisation** avec un défi de code PKCE (code challenge).

3. **L’utilisateur se connecte à Emailit**, choisit les espaces de travail que votre application peut utiliser et approuve la portée demandée.

4. **Emailit redirige** vers votre URI de redirection avec un code d’autorisation à usage unique.

5. **Échangez le code** contre un jeton d’accès valable 15 minutes et un jeton d’actualisation.

6. **Appelez l’API** avec le jeton d’accès, et actualisez-le quand il expire.

## Endpoints

| Méthode | Chemin | Rôle |
| --- | --- | --- |
| `GET` | `/.well-known/oauth-authorization-server` | Métadonnées du serveur d’autorisation (RFC 8414) |
| `GET` | `/.well-known/oauth-protected-resource` | Métadonnées de ressource protégée pour l’API REST |
| `GET` | `/.well-known/oauth-protected-resource/mcp` | Métadonnées de ressource protégée pour le serveur MCP |
| `POST` | `/oauth/register` | Enregistrement dynamique de client (RFC 7591) |
| `GET` | `/oauth/authorize` | Connexion et consentement dans le navigateur |
| `POST` | `/oauth/token` | Échanger un code ou un jeton d’actualisation |
| `POST` | `/oauth/revoke` | Révoquer une autorisation avec son jeton d’actualisation (RFC 7009) |
| `GET` | `/oauth/grants` | Lister les autorisations (celles de l’utilisateur connecté, ou celles d’un espace de travail avec une clé API) |
| `POST` | `/oauth/grants/:id/revoke` | Révoquer une autorisation |
| `PUT` | `/oauth/grants/:id/workspaces` | Modifier les espaces de travail qu’une autorisation peut utiliser (par l’utilisateur qui a connecté l’application) |

Tous les chemins se trouvent sur `https://api.emailit.com`.

## Découvrir le serveur

```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"]
}
```

Les clients MCP partent plutôt des métadonnées de ressource protégée. Une requête non authentifiée vers `https://api.emailit.com/mcp` renvoie `401` avec `WWW-Authenticate: Bearer resource_metadata="https://api.emailit.com/.well-known/oauth-protected-resource/mcp"`, et ce document pointe vers le serveur d’autorisation :

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

## Portées

| Portée | Accès accordé |
| --- | --- |
| `sending` | Envoi et transfert d’e-mails, et reprogrammation, annulation et relance des envois. Le même accès qu’une clé API Sending Only. |
| `full` | Tous les endpoints de l’API REST et tous les outils MCP. Le même accès qu’une clé API Full Access. |

`full` inclut déjà tout ce que permet `sending`. Demandez `sending` si votre application ne fait qu’envoyer, et `full` sinon ; la valeur `sending full`, séparée par une espace, est aussi acceptée. Si vous omettez `scope`, Emailit utilise toutes les portées enregistrées par le client.

## Enregistrer votre client

Vous pouvez enregistrer un client de deux façons. Les deux fonctionnent avec tous les flux de cette page.

### Enregistrement dynamique de client

Envoyez une requête d’enregistrement. Aucune authentification n’est nécessaire, et chaque adresse IP peut enregistrer jusqu’à 20 clients par heure.

```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"
  }'
```

| Champ | Obligatoire | Description |
| --- | --- | --- |
| `client_name` | Oui | Affiché sur la page de consentement. 200 caractères au maximum. |
| `redirect_uris` | Oui | De 1 à 10 URI de redirection. Voir [Règles des URI de redirection](#redirect-uri-rules). |
| `grant_types` | Non | Doit inclure `authorization_code` ; peut inclure `refresh_token`. Par défaut : les deux. |
| `response_types` | Non | Uniquement `code`. |
| `scope` | Non | Portées séparées par des espaces. Par défaut : `sending full`. |
| `token_endpoint_auth_method` | Non | `none` (par défaut) pour les clients publics comme les applications de bureau, mobiles et web ; `client_secret_basic` ou `client_secret_post` pour les applications côté serveur. |
| `client_uri` | Non | Page d’accueil de votre application. |
| `logo_uri` | Non | Logo affiché sur la page de consentement. |

La réponse `201` reprend les métadonnées et ajoute un `client_id`. Les clients confidentiels reçoivent aussi un `client_secret`, affiché une seule fois :

```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` vaut toujours `0` : les secrets n’expirent pas. Tous les clients, publics ou confidentiels, doivent utiliser PKCE.

### Document de métadonnées d’ID client

Si votre application peut héberger un fichier JSON statique, vous pouvez vous passer de l’enregistrement. Publiez un document à une URL HTTPS et utilisez cette URL comme `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"
}
```

- Le `client_id` du document doit être exactement égal à l’URL du document.
- `token_endpoint_auth_method` doit valoir `none`. Ce sont des clients publics qui s’appuient sur PKCE.
- Les URI de redirection HTTPS doivent se trouver sur le même hôte que le document.
- Emailit récupère le document pendant l’autorisation, avec un timeout de 5 secondes et sans suivre les redirections, et le met en cache pendant 5 minutes.
- La page de consentement affiche l’hôte du document, par exemple `crm.acme.com`, au lieu de `client_name`.

Les clients IA utilisent cette méthode : ChatGPT, Claude et Grok s’identifient avec leurs propres documents de métadonnées d’ID client et se connectent donc sans enregistrement préalable. Pour Grok, Emailit accepte aussi les redirections vers `console.x.ai`.

### Règles des URI de redirection

- Les URI `https://` sont autorisées.
- `http://` n’est autorisé que pour les hôtes de bouclage (loopback) : `127.0.0.1`, `localhost` et `[::1]`. Pour les URI de bouclage, le port peut différer au moment de l’autorisation, mais l’hôte, le chemin et la chaîne de requête doivent correspondre. `localhost` et `127.0.0.1` sont des hôtes différents.
- Les schémas à usage privé comme `cursor://`, `vscode://` ou `com.acme.crm://` sont autorisés pour les applications natives.
- Les URI `file`, `ftp`, `data`, `javascript`, `blob`, `about` et `vbscript`, ainsi que toute URI contenant un `#fragment`, sont rejetées.
- En dehors des ports de bouclage, le `redirect_uri` que vous envoyez doit correspondre exactement à une URI enregistrée.

## Envoyer l’utilisateur vers Emailit

Pour chaque autorisation, créez un vérificateur de code (code verifier) et un défi PKCE, ainsi qu’un `state` aléatoire :

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

Redirigez ensuite le navigateur de l’utilisateur vers l’endpoint d’autorisation :

```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
```

| Paramètre | Obligatoire | Description |
| --- | --- | --- |
| `response_type` | Oui | `code` |
| `client_id` | Oui | Votre ID client, ou l’URL de votre document de métadonnées. |
| `redirect_uri` | Oui | L’une de vos URI de redirection enregistrées. |
| `code_challenge` | Oui | SHA-256 du vérificateur de code, encodé en Base64url. |
| `code_challenge_method` | Recommandé | `S256`. `plain` n’est pas pris en charge. |
| `scope` | Recommandé | `sending` ou `full`. Doit être une portée enregistrée par le client. |
| `state` | Recommandé | Une valeur aléatoire que vous vérifiez au retour (callback). |
| `resource` | Non | La ressource que vous voulez appeler (RFC 8707) : `https://api.emailit.com` pour l’API REST ou `https://api.emailit.com/mcp` pour MCP. Dans les deux cas, les jetons fonctionnent sur les deux. |

Ouvrez cette URL dans le navigateur de l’utilisateur. Ne la récupérez pas depuis votre backend.

## Ce que voit l’utilisateur

1. **Connexion à Emailit.** L’utilisateur saisit son adresse e-mail et son mot de passe. S’il utilise une application d’authentification pour l’authentification à deux facteurs, il saisit ensuite son code ou un code de récupération.
2. **Choix des espaces de travail.** La page affiche votre logo, le nom de votre client (ou l’hôte de votre document de métadonnées) et les portées demandées. L’utilisateur choisit **All my workspaces**, qui inclut les espaces de travail qu’il créera ou rejoindra plus tard, ou **Only these workspaces** avec ceux qu’il coche, puis choisit l’espace de travail dans lequel votre application démarre.
3. **Autorisation de l’accès.** Il sélectionne **Allow access** ou **Deny**.

Une autorisation peut couvrir plusieurs espaces de travail. L’utilisateur peut modifier la liste plus tard sous **Account > Connected apps** dans le tableau de bord, et votre application voit la modification à sa requête suivante.

Après approbation, Emailit redirige vers votre URI de redirection :

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

Vérifiez que `state` correspond à la valeur enregistrée et que `iss` vaut `https://api.emailit.com`. Le code est valable 10 minutes et ne peut être utilisé qu’une fois. Si l’utilisateur sélectionne **Deny**, la redirection contient `error=access_denied` à la place.

## Échanger le code contre des jetons

Envoyez une requête POST à l’endpoint de jeton au format `application/x-www-form-urlencoded` (le JSON est aussi accepté). Envoyez le même `redirect_uri` et le vérificateur de code d’origine :

**Client confidentiel**

```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"
```

  Avec `client_secret_basic`, envoyez l’ID client et le secret dans l’en-tête `Authorization: Basic`. Avec `client_secret_post`, envoyez plutôt `client_id` et `client_secret` comme champs de formulaire. N’envoyez pas le secret des deux façons.

**Client public**

```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"
```

  Les clients publics envoient `client_id` sans secret. PKCE prouve que c’est le même client qui a lancé le flux.

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

Le jeton d’accès est valable 15 minutes (`expires_in` est en secondes). Traitez-le comme une chaîne opaque : ne l’analysez pas et ne vous fiez pas à son contenu.

## Appeler l’API

Utilisez le jeton d’accès comme jeton bearer sur l’API REST et le serveur MCP, exactement comme une clé API :

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

Les requêtes REST s’exécutent dans l’espace de travail par défaut de l’autorisation : celui dans lequel l’utilisateur a choisi de démarrer, ou celui qu’il a choisi ensuite. Les outils MCP peuvent aussi cibler un autre espace de travail autorisé avec leur argument `workspace`. Chaque requête utilise la portée accordée et le rôle de l’utilisateur dans l’espace de travail : une requête hors de la portée, ou une route réservée au rôle Admin appelée par un membre ayant le rôle Member, renvoie `403`. Si l’utilisateur quitte un espace de travail, l’autorisation le perd aussi.

## Actualiser les jetons

Avant l’expiration du jeton d’accès, ou quand une requête renvoie `401`, obtenez-en un nouveau :

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

- **Rotation.** Chaque actualisation renvoie un nouveau `refresh_token` valable 60 jours. Enregistrez-le et supprimez l’ancien. Tant que vous actualisez dans les 60 jours, la connexion n’expire pas.
- **Détection de réutilisation.** Si un ancien jeton d’actualisation est réutilisé plus d’une minute après son remplacement, Emailit renvoie `invalid_grant` (`Refresh token reuse detected`) et révoque toute l’autorisation. L’utilisateur doit alors autoriser de nouveau l’application. Sérialisez les actualisations pour que deux workers n’utilisent jamais le même jeton d’actualisation.
- **Portée plus restreinte.** Vous pouvez transmettre `scope` pour obtenir un jeton d’accès avec moins de portées que l’autorisation. Vous ne pouvez pas en demander davantage.

## Révoquer une autorisation

Quand un utilisateur déconnecte Emailit de votre application, révoquez l’autorisation avec son jeton d’actualisation :

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

L’endpoint authentifie votre client de la même façon que l’endpoint de jeton et renvoie toujours `200` avec un corps vide. Révoquer le jeton d’actualisation révoque toute l’autorisation : ses jetons d’accès cessent de fonctionner dès la requête suivante. Les jetons d’accès ne peuvent pas être révoqués seuls ; `token_type_hint=access_token` renvoie `invalid_request`.

Les utilisateurs peuvent aussi révoquer l’accès de votre application de leur côté, sous [Applications connectées](/fr/docs/account/connected-apps/) dans le tableau de bord, ou avec l’API des autorisations ci-dessous.

## Gérer les autorisations

L’API des autorisations liste et modifie les autorisations du côté de l’utilisateur. Elle accepte la session du tableau de bord de l’utilisateur ou une clé API Full Access :

| Appelant | `GET /oauth/grants` | `POST /oauth/grants/:id/revoke` | `PUT /oauth/grants/:id/workspaces` |
| --- | --- | --- | --- |
| L’utilisateur qui a connecté l’application | Ses autorisations dans tous les espaces de travail | Révoque toute l’autorisation | Modifie les espaces de travail de l’autorisation |
| Clé API Full Access | Autorisations qui incluent l’espace de travail de la clé | Retire l’espace de travail de la clé de l’autorisation ; l’autorisation est révoquée quand il ne reste plus aucun espace de travail | Non autorisé (`403`) |

Chaque autorisation de la liste a cette forme :

```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
}
```

Pour modifier les espaces de travail d’une autorisation, envoyez `access` (`all` ou `selected`), `workspace_ids` pour `selected`, et éventuellement `default_workspace_id`, qui doit être l’un des espaces de travail autorisés :

```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" }'
```

L’utilisateur doit être membre de chaque espace de travail qu’il sélectionne. Une autorisation révoquée ne peut pas être modifiée (`409`) ; l’application doit se connecter de nouveau.

## Erreurs

Les endpoints OAuth renvoient les erreurs au format OAuth, et non au [format d’erreur de l’API REST](/fr/docs/api-reference/errors/) :

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

| Erreur | Statut | Cas |
| --- | --- | --- |
| `invalid_request` | 400 | Un paramètre est absent ou invalide, le client est inconnu lors de l’autorisation, le `redirect_uri` n’est pas enregistré, `code_challenge_method` ne vaut pas `S256`, ou un secret client a été envoyé à la fois dans l’en-tête et dans le corps. |
| `invalid_client` | 401 | L’endpoint de jeton ou de révocation ne peut pas authentifier le client : client inconnu, secret absent ou incorrect. |
| `invalid_grant` | 400 | Le code est invalide, déjà utilisé ou expiré ; le `redirect_uri` ou le vérificateur PKCE ne correspond pas ; ou le jeton d’actualisation est invalide, expiré, révoqué ou réutilisé. |
| `invalid_scope` | 400 | La portée n’est pas prise en charge, n’a pas été enregistrée pour le client ou dépasse l’autorisation lors d’une actualisation. |
| `unsupported_response_type` | 400 | `response_type` ne vaut pas `code`. |
| `unsupported_grant_type` | 400 | `grant_type` ne vaut ni `authorization_code` ni `refresh_token`. |
| `access_denied` | Redirection | L’utilisateur a sélectionné **Deny**. |
| `too_many_requests` | 429 | Plus de 20 enregistrements par heure depuis une même adresse IP. |
| `server_error` | 500 | Un problème est survenu du côté d’Emailit. Réessayez. |

Tant que le `client_id` et le `redirect_uri` ne sont pas validés, les erreurs d’autorisation s’affichent en JSON dans le navigateur et ne sont jamais redirigées. Ensuite, les erreurs ne sont redirigées vers votre callback (avec `error`, `error_description`, `state` et `iss`) que pour les URI de redirection de bouclage et à usage privé, et pour les clients à document de métadonnées ; les autres clients reçoivent une page d’erreur JSON. Un **Deny** de l’utilisateur est toujours redirigé.

## Liste de contrôle de sécurité

- Gardez `client_secret` et les jetons d’actualisation sur votre serveur, chiffrés au repos. Ne livrez jamais de secret client dans une application mobile, de bureau ou web ; utilisez plutôt un client public avec PKCE.
- Générez un nouveau `state` et un nouveau vérificateur de code pour chaque autorisation, et vérifiez `state` et `iss` au retour (callback).
- Enregistrez des URI de redirection exactes. N’utilisez pas de redirecteurs ouverts comme callbacks.
- Enregistrez chaque autorisation avec l’espace de travail auquel elle appartient, et gérez `invalid_grant` en demandant à l’utilisateur de se reconnecter.
- Demandez `sending` si vous ne faites qu’envoyer des e-mails.

## Voir aussi

  - [Espaces de travail et droits d’accès](/fr/docs/mcp/workspaces-and-permissions/): Autorisations, durée de vie des jetons et révocation.
  - [Référence de l’API](/fr/docs/api-reference/): Les endpoints que vos jetons peuvent appeler.
  - [Clés API](/fr/docs/developers/api-keys/): Pour vos propres scripts, les clés sont plus simples.
  - [Serveur MCP](/fr/docs/mcp/): Le flux OAuth en pratique.

---
Source: https://emailit.com/fr/docs/developers/oauth-apps/
