API & intégration
Sign in with Capibara (OAuth)
Mis à jour le 24 septembre 2026Plateforme v1 Markdown brut
Deux grants, deux usages
Capibara expose un fournisseur OAuth 2.0 maison (JWT HMAC-SHA256, sans
dépendance externe) avec deux grants réellement fonctionnels :
| Grant | Usage | Nécessite un utilisateur ? |
|---|---|---|
client_credentials |
Votre backend appelle l’API pour son propre compte (tâches de fond, synchronisation). | non |
authorization_code + PKCE |
« Se connecter avec Capibara » — un utilisateur autorise votre application à agir en son nom. | oui, avec écran de consentement |
Les deux grants émettent le même type de jeton : un JWT compact signé
HS256, valable 1 heure (expires_in: 3600). Ce jeton est opaque pour
vous : vous pouvez le décoder pour en lire l’expiration, mais vous ne
pouvez pas vérifier sa signature (elle est scellée avec un secret serveur) —
utilisez-le tel quel comme Authorization: Bearer <token>, il n’y a ni
endpoint d’introspection ni JWKS public à ce stade.
Décodé, il porte typ: "access" (son usage), knd (client pour
client_credentials, user pour authorization_code), sub,
cid (votre client_id), scopes, iat et exp. Un jeton sans
typ (émis avant son introduction) est refusé : redemandez-en un.
Débit limité : POST /api/oauth/token accepte au plus 30 requêtes par
5 minutes par IP appelante ; au-delà, 429 avec un en-tête
Retry-After: 300.
Créer un client OAuth
Depuis /dev/keys, section « Clients OAuth » :
| Champ | Contrainte |
|---|---|
| Nom | 2 à 80 caractères — affiché à l’utilisateur sur l’écran de consentement. |
| URLs de redirection | jusqu’à 10, chacune une URL absolue en https:// (http:// accepté seulement vers localhost, 127.0.0.1 ou [::1], pour le développement), sans identifiants ni fragment #. Doivent correspondre exactement à l’URL utilisée dans la requête d’autorisation. |
| Scopes | sous-ensemble de : crm:read, crm:write, shop:read, shop:write, billing:read, billing:write, site:read, media:read, projects:read, projects:write, planning:read, planning:write, formation:read (familles de module ; l’accès aux données d’une organisation exige en plus les scopes feuilles accordés à l’installation). |
La création renvoie un client_id (cid_…) et un client_secret
(csec_…) affichés une seule fois — seul le hash du secret est
conservé côté serveur. Notez-les tout de suite dans votre gestionnaire de
secrets.
Grant client_credentials (serveur-à-serveur)
POST https://capibara.fr/api/oauth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "cid_VOTRE_CLIENT_ID",
"client_secret": "csec_VOTRE_CLIENT_SECRET",
"scope": "crm:read shop:read"
}
Réponse (200) :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "crm:read shop:read"
}
scope est optionnel dans la requête : si omis, le jeton reçoit tous les
scopes déjà accordés au client. S’il est fourni, seule l’intersection
entre les scopes demandés et ceux du client est accordée — demander un scope
que le client ne possède pas ne provoque pas d’erreur, il est simplement
absent du jeton final.
Le corps peut aussi être envoyé en application/x-www-form-urlencoded
(format historique OAuth) — les deux formats sont acceptés par l’endpoint.
Grant authorization_code + PKCE (connexion utilisateur)
PKCE (S256 uniquement — plain est refusé) est obligatoire, y
compris pour un client confidentiel : c’est la seule méthode de preuve de
possession acceptée par cet endpoint.
1. Générez un code_verifier et son code_challenge
const crypto = require('node:crypto');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
Conservez codeVerifier en mémoire côté serveur (associé à la session de
l’utilisateur qui démarre le flux) — vous en aurez besoin à l’étape 4.
2. Redirigez l’utilisateur vers l’autorisation
GET https://capibara.fr/api/oauth/authorize
?response_type=code
&client_id=cid_VOTRE_CLIENT_ID
&redirect_uri=https%3A%2F%2Fmon-app.example%2Foauth%2Fcallback
&scope=crm%3Aread%20shop%3Aread
&state=UN_JETON_ANTI_CSRF_ALEATOIRE
&code_challenge=LE_CODE_CHALLENGE_CALCULE
&code_challenge_method=S256
Validations effectuées par cet endpoint avant de continuer : response_type
doit valoir code, client_id/redirect_uri/code_challenge sont
requis, code_challenge_method doit être S256, le client doit exister et
redirect_uri doit correspondre exactement à l’une de ses URLs
enregistrées. Les scopes accordés sont réduits à l’intersection avec ceux du
client, silencieusement (pas d’erreur si vous en demandez un de trop).
Tant que le client et son redirect_uri ne sont pas validés (client
inconnu, redirect_uri absent, non enregistré ou non sûr), la réponse est
un JSON 400 { "error": "invalid_client" | "invalid_request", "error_description": "…" },
sans redirection : Capibara ne renvoie jamais un utilisateur vers une
adresse que vous n’avez pas enregistrée. Une fois ce couple validé, les
autres erreurs repartent vers votre redirect_uri avec
?error=<code>&error_description=…&state=… — traitez donc error sur
votre callback.
Si tout est valide, l’utilisateur est redirigé vers l’écran de consentement
Capibara (/oauth/consent) — connecté d’abord si nécessaire.
3. L’utilisateur autorise (ou refuse)
L’écran de consentement affiche le nom de votre application, votre nom de
développeur, et la liste des scopes demandés en clair (ex. crm:read). Deux
issues :
- Refus → redirection vers
redirect_uri?error=access_denied&state=…. - Acceptation → un code d’autorisation à usage unique est généré (valable
10 minutes), et l’utilisateur est redirigé vers :
redirect_uri?code=UN_CODE&state=…
4. Échangez le code contre un jeton
POST https://capibara.fr/api/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "LE_CODE_RECU_SUR_VOTRE_REDIRECT_URI",
"client_id": "cid_VOTRE_CLIENT_ID",
"redirect_uri": "https://mon-app.example/oauth/callback",
"code_verifier": "LE_CODE_VERIFIER_DE_L_ETAPE_1"
}
Réponse (200) — identique en forme à celle du grant client_credentials :
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "crm:read shop:read"
}
Note. Cet échange ne demande pas
client_secret: la preuve de
possession repose uniquement surcode_verifier(PKCE), ce qui permet à
ce même grant de fonctionner pour une application qui ne peut pas garder un
secret confidentiel (mobile, SPA). Le code est vérifié surclient_id,
redirect_uri(doit correspondre exactement à celui de l’étape 2) et
code_verifier(son empreinte SHA-256 doit correspondre au
code_challengeenvoyé à l’étape 2) ; il est consommé au premier
échange réussi — le réutiliser renvoieinvalid_grant.
Erreurs standard renvoyées
Sur POST /api/oauth/token (corps JSON { "error": "<code>" }) :
| HTTP | error |
Quand |
|---|---|---|
| 400 | invalid_request |
paramètre requis manquant (code, code_verifier, client_secret…). |
| 401 | invalid_client |
client_id/client_secret incorrects, ou client inconnu (grant client_credentials). |
| 400 | invalid_grant |
code d’autorisation expiré, déjà consommé, ou ne correspondant pas au client_id/redirect_uri/code_verifier fournis. |
| 400 | unsupported_grant_type |
grant_type ni client_credentials ni authorization_code. |
| 400 | unauthorized_client |
votre compte développeur est suspendu. |
| 429 | rate_limited |
plus de 30 requêtes/5 min depuis la même IP. En-tête Retry-After: 300. |
Sur GET /api/oauth/authorize, en JSON 400, sans redirection (le
couple client + adresse n’est pas validé) :
error |
Quand |
|---|---|
invalid_request |
client_id ou redirect_uri manquant ; redirect_uri non enregistré pour ce client, ou non sûr. |
invalid_client |
client inconnu. |
Puis en paramètres de la redirection vers votre redirect_uri :
error |
Quand |
|---|---|
unsupported_response_type |
response_type ≠ code. |
invalid_request |
code_challenge manquant ; code_challenge_method ≠ S256. |
unauthorized_client |
votre compte développeur est suspendu. |
access_denied |
l’utilisateur a refusé le consentement. |
Utiliser le jeton obtenu
Le jeton du grant client_credentials s’utilise contre l’API
publique, comme une clé d’API. Le jeton du grant
authorization_code (émis au nom d’un utilisateur) n’ouvre pas encore
l’API publique : ce qu’il donnera le droit de lire est en cours de
définition. Tout jeton est revérifié à chaque appel : un client supprimé ou
un compte développeur suspendu le rend inutilisable avant son expiration.
GET https://capibara.fr/api/public/v1/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…