Guide
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.
Fonctionnement
-
Enregistrez un client avec l’enregistrement dynamique de client, ou hébergez un document de métadonnées d’ID client.
-
Envoyez l’utilisateur vers l’URL d’autorisation avec un défi de code PKCE (code challenge).
-
L’utilisateur se connecte à Emailit, choisit les espaces de travail que votre application peut utiliser et approuve la portée demandée.
-
Emailit redirige vers votre URI de redirection avec un code d’autorisation à usage unique.
-
Échangez le code contre un jeton d’accès valable 15 minutes et un jeton d’actualisation.
-
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
curl https://api.emailit.com/.well-known/oauth-authorization-server{
"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 :
{
"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.
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. |
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 :
{
"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 :
{
"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_iddu document doit être exactement égal à l’URL du document. token_endpoint_auth_methoddoit valoirnone. 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 declient_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,localhostet[::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.localhostet127.0.0.1sont des hôtes différents.- Les schémas à usage privé comme
cursor://,vscode://oucom.acme.crm://sont autorisés pour les applications natives. - Les URI
file,ftp,data,javascript,blob,aboutetvbscript, ainsi que toute URI contenant un#fragment, sont rejetées. - En dehors des ports de bouclage, le
redirect_urique 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 :
import { createHash, randomBytes } from 'node:crypto';
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 :
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
- 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.
- 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.
- 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 :
https://crm.acme.com/oauth/emailit/callback?code=XvRKyG5Pkplrd3xkRNaEayKkpkebEMdF&state=Jq3k9V0n2xR7bLm1&iss=https%3A%2F%2Fapi.emailit.comVé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 :
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.
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.
{
"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 :
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 :
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_tokenvalable 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
scopepour 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 :
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 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 :
{
"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 :
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 :
{
"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_secretet 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
stateet un nouveau vérificateur de code pour chaque autorisation, et vérifiezstateetissau 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_granten demandant à l’utilisateur de se reconnecter. - Demandez
sendingsi vous ne faites qu’envoyer des e-mails.