Flux d'installation OAuth
Une application accède à une boutique par le flux OAuth 2.0 avec code d'autorisation et PKCE. Le marchand approuve votre application sur un écran de consentement DZBuild, votre serveur échange le code contre un jeton d'accès par boutique, et chaque jeton appelle l'API REST pour sa seule boutique.
Endpoints
| Étape | Requête | Qui l'envoie |
|---|---|---|
| Autorisation | GET https://dzbuild.com/oauth/apps/authorize | Le navigateur du marchand, envoyé par votre application |
| Approbation ou refus | POST https://dzbuild.com/oauth/apps/approve et /oauth/apps/deny | Le formulaire de consentement. Votre application ne les appelle jamais. |
| Échange du code | POST https://dzbuild.com/oauth/apps/token | Votre serveur |
| Appels API | https://api.dzbuild.app/v1/... | Votre serveur, avec le jeton d'accès |
Le flux
- Votre serveur crée un vérificateur PKCE, son défi S256 et un
statealéatoire. - Il envoie le marchand vers l'URL d'autorisation. Un marchand non connecté se connecte d'abord puis revient sur la même URL.
- DZBuild affiche l'écran de consentement. Le marchand choisit les boutiques et approuve.
- DZBuild redirige le navigateur vers votre
redirect_uriavec uncodeet votrestate. - Votre serveur envoie le code, le vérificateur et ses identifiants client à l'endpoint de jeton.
- La réponse contient un jeton d'accès pour chaque boutique installée.
Étape 1 : envoyer le marchand vers l'URL d'autorisation
| Paramètre | Obligatoire | Règle |
|---|---|---|
response_type | oui | Toujours code. |
client_id | oui | L'identifiant client de votre application, dans la console développeur : dzapp_ suivi de 20 caractères hexadécimaux. |
redirect_uri | oui | Une des adresses de redirection enregistrées sur l'application, caractère pour caractère. Aucune correspondance par préfixe ou par motif. |
scope | non | Scopes séparés par des espaces. Chaque scope doit être enregistré sur l'application et autorisé pour les applications. Absent ou vide : tous les scopes enregistrés sur l'application. 512 caractères au plus. |
state | oui | 1 à 1024 caractères. DZBuild le renvoie sans modification. Liez-le à la session du marchand et vérifiez-le au retour. |
code_challenge | oui | SHA-256 du vérificateur en base64url, sans remplissage : exactement 43 caractères parmi A-Z a-z 0-9 - _. |
code_challenge_method | oui | Toujours S256. La méthode plain est refusée. |
Le vérificateur de code compte 43 à 128 caractères parmi A-Z a-z 0-9 - . _ ~. Gardez-le sur votre serveur jusqu'à l'échange du code. Il ne passe jamais par le navigateur.
PKCE en PHP :
<?php
function base64url(string $bytes): string
{
return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}
$verifier = base64url(random_bytes(32)); // 43 characters
$challenge = base64url(hash('sha256', $verifier, true));
// Keep $verifier on your server (session or database) until the token call.
$authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' . http_build_query([
'response_type' => 'code',
'client_id' => 'dzapp_0123456789abcdef0123',
'redirect_uri' => 'https://app.example.com/dzbuild/callback',
'scope' => 'orders:read products:read',
'state' => bin2hex(random_bytes(16)),
'code_challenge' => $challenge,
'code_challenge_method' => 'S256',
], '', '&', PHP_QUERY_RFC3986);
PKCE en Node.js :
const crypto = require('node:crypto');
const verifier = crypto.randomBytes(32).toString('base64url'); // 43 characters
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
// Keep the verifier on your server (session or database) until the token call.
const authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' + new URLSearchParams({
response_type: 'code',
client_id: 'dzapp_0123456789abcdef0123',
redirect_uri: 'https://app.example.com/dzbuild/callback',
scope: 'orders:read products:read',
state: crypto.randomBytes(16).toString('hex'),
code_challenge: challenge,
code_challenge_method: 'S256',
});
Pour tester votre propre code : le vérificateur d'exemple de la RFC 7636, dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk, doit donner le défi E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM.
Étape 2 : l'écran de consentement
L'écran de consentement affiche le nom, le logo et le développeur de votre application. Une application approuvée porte un badge « vérifiée par DZBuild ». Une application pas encore approuvée affiche un avis de mode test, et seul son développeur peut l'ouvrir.
Le marchand voit les boutiques dont il est propriétaire. Les membres d'équipe ne voient aucune boutique, car une installation donne un jeton pour toute la boutique. Une boutique qui ne peut pas recevoir l'application est affichée avec la raison : le marchand n'en est pas propriétaire, l'application est en mode test, le plan de la boutique est inférieur au plan minimum de l'application, ou l'application est suspendue. Le marchand peut approuver jusqu'à 10 boutiques à la fois.
Les scopes demandés sont regroupés par ressource. La demande de consentement reste valable 10 minutes.
Étape 3 : traiter la redirection
Après l'approbation, DZBuild répond 302 vers votre adresse de redirection :
HTTP/1.1 302 Found
Location: https://app.example.com/dzbuild/callback?code=3f9a...64-hex...&state=c2a8a879c9434b775191caf5b17d846f
Le code fait 64 caractères hexadécimaux, reste valable 10 minutes et ne sert qu'une fois. Vérifiez que state correspond à la valeur enregistrée avant d'utiliser le code. Si votre adresse de redirection enregistrée contient déjà une chaîne de requête, DZBuild ajoute ses paramètres avec &.
Erreurs d'autorisation et de consentement
Les erreurs vous arrivent de deux façons. Tant que DZBuild n'a pas reconnu votre client_id et votre redirect_uri, il affiche une page d'erreur au marchand et ne redirige jamais : un lien erroné ne peut pas envoyer de code vers une adresse inconnue. Ensuite, il redirige vers votre redirect_uri avec un paramètre error. Les contrôles s'exécutent dans l'ordre de ce tableau.
| Condition | Résultat | Renvoyé comme |
|---|---|---|
client_id inconnu ou mal formé, ou redirect_uri non enregistrée à l'identique | Page d'erreur, HTTP 400 | Page pour le marchand |
| Application non approuvée et le marchand n'est pas son développeur, ou application refusée ou suspendue | Page d'erreur, HTTP 404 | Page pour le marchand |
response_type différent de code | error=unsupported_response_type | Redirection, avec state quand state est valide |
state absent ou plus long que 1024 caractères | error=invalid_request | Redirection, sans state |
code_challenge différent de 43 caractères base64url, ou code_challenge_method différent de S256 | error=invalid_request | Redirection, avec state |
Un scope non enregistré sur l'application, non autorisé pour les applications, ou scope plus long que 512 caractères | error=invalid_scope | Redirection, avec state |
| Le marchand clique sur Refuser | error=access_denied | Redirection, avec state |
| Aucune boutique choisie | Page d'erreur, HTTP 400 | Page pour le marchand |
| Plus de 10 boutiques choisies | Page d'erreur, HTTP 400 | Page pour le marchand |
| Une boutique choisie n'appartient pas au marchand ou ne peut pas recevoir l'application | Page d'erreur, HTTP 403 | Page pour le marchand |
| Demande de consentement expirée ou déjà traitée | Page d'erreur, HTTP 400 | Page pour le marchand |
Étape 4 : échanger le code
Votre serveur envoie un formulaire (application/x-www-form-urlencoded) à l'endpoint de jeton.
| Champ | Valeur |
|---|---|
grant_type | authorization_code |
code | Le code reçu dans la redirection. |
redirect_uri | La même chaîne que celle envoyée à l'URL d'autorisation. |
code_verifier | Le vérificateur derrière votre code_challenge. |
client_id | Votre identifiant client. |
client_secret | Votre client secret (dzas_ suivi de 48 caractères hexadécimaux). |
Vous pouvez envoyer client_id et client_secret en identifiants HTTP Basic au lieu de champs de formulaire. DZBuild lit d'abord les champs du formulaire et n'utilise l'en-tête Authorization: Basic que si aucun des deux champs n'est présent. Dans Basic, encodez les deux valeurs en form-urlencoded comme l'exige la section 2.3.1 de la RFC 6749.
Envoyez un en-tête User-Agent sur cette requête et sur chaque appel à l'API, par exemple my-app/1.0 (+https://example.com). dzbuild.com répond à un POST sans cet en-tête, y compris cette requête de jeton, par un 403 et une page de vérification HTML au lieu de JSON. Le fetch de Cloudflare Workers et plusieurs bibliothèques HTTP n'envoient pas de User-Agent si vous n'en fixez pas un ; curl envoie le sien.
curl -sS https://dzbuild.com/oauth/apps/token \
-H "Accept: application/json" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=https://app.example.com/dzbuild/callback" \
--data-urlencode "code_verifier=$CODE_VERIFIER" \
--data-urlencode "client_id=$DZBUILD_CLIENT_ID" \
--data-urlencode "client_secret=$DZBUILD_CLIENT_SECRET"
Un échange réussi répond 200 avec Cache-Control: no-store :
{
"access_token": "dzpk_live_...",
"token_type": "Bearer",
"scope": "orders:read products:read",
"store_id": 141,
"install_id": 57,
"stores": [
{
"store_id": 141,
"store_name": "Boutique Amel",
"install_id": 57,
"access_token": "dzpk_live_..."
},
{
"store_id": 152,
"store_name": "Amel Kids",
"install_id": 58,
"access_token": "dzpk_live_..."
}
]
}
| Champ | Signification |
|---|---|
access_token | Le jeton de la première entrée de stores. |
token_type | Toujours Bearer. |
scope | Les scopes accordés, séparés par des espaces. Chaque boutique de la réponse reçoit les mêmes scopes. |
store_id, install_id | La première entrée de stores. |
stores | Une entrée par boutique installée : store_id, store_name, install_id, access_token. |
La réponse ne contient ni expires_in ni refresh_token. Conservez chaque jeton côté serveur, indexé par store_id. Ce jeton d'accès par boutique s'appelle le jeton d'installation dans les autres pages.
Erreurs d'échange
Chaque erreur est un JSON avec error et error_description, et Cache-Control: no-store.
| HTTP | error | error_description | Cause |
|---|---|---|---|
| 401 | invalid_client | Client authentication failed | client_id ou client_secret absent ou faux. Avec des identifiants Basic, la réponse porte aussi WWW-Authenticate: Basic realm="dzbuild". |
| 400 | unsupported_grant_type | Only authorization_code is supported | grant_type différent de authorization_code. |
| 400 | invalid_grant | The code is invalid, expired, already used, or does not match this request | Code inconnu, expiré ou déjà utilisé, code émis pour une autre application, redirect_uri différente, ou vérificateur qui ne correspond pas au défi. |
| 400 | invalid_grant | No approved store could be installed | Aucune boutique choisie n'a pu être installée : chacune a échoué au contrôle final (par exemple un plan inférieur au plan minimum de l'application) ou son installation a échoué. |
| 500 | server_error | The code could not be checked ou The install could not be completed | Une panne de la plateforme. Renvoyez le marchand vers l'étape d'autorisation. |
DZBuild vérifie d'abord le client, puis grant_type, puis consomme le code. Un code consommé est perdu même quand le redirect_uri ou le vérificateur est faux : un échange raté ne peut pas être rejoué avec le même code. Lancez une nouvelle demande d'autorisation.
Installations sur plusieurs boutiques
Un marchand propriétaire de plusieurs boutiques peut en approuver jusqu'à 10 en un seul consentement. La réponse de l'échange liste chaque boutique installée dans stores, avec son propre install_id et son propre access_token. Un jeton n'atteint que sa boutique.
DZBuild contrôle chaque boutique une nouvelle fois pendant l'échange. Une boutique qui ne remplit plus les conditions est écartée, donc stores peut contenir moins de boutiques que le marchand n'en a cochées. Lisez stores, pas le consentement que vous attendiez.
Relancer le flux pour une boutique qui a déjà votre application met à jour la même installation : l'install_id reste, les scopes deviennent ceux du nouveau consentement, et un nouveau jeton remplace l'ancien. L'ancien jeton cesse de fonctionner immédiatement.
Appeler l'API
Envoyez le jeton dans un en-tête Bearer vers https://api.dzbuild.app/v1. GET /v1/whoami ne demande aucun scope et montre ce qu'un jeton peut faire :
curl -sS https://api.dzbuild.app/v1/whoami \
-H "Authorization: Bearer $DZBUILD_ACCESS_TOKEN"
{
"data": {
"key_id": "...",
"store_id": 141,
"type": "platform",
"rate_limit_tier": "enterprise",
"pilot": true,
"scopes": ["orders:read", "products:read"],
"app": {
"app_id": 12,
"client_id": "dzapp_0123456789abcdef0123",
"install_id": 57
}
},
"meta": {
"request_id": "...",
"api_version": "v1"
}
}
Les jetons d'application fonctionnent avec tous les plans de boutique. Le niveau enterprise affiché par whoami est l'étiquette que l'API donne aux jetons d'application ; les limites propres à votre application sont sur la page limites de requêtes.
Durée de vie et révocation du jeton
Un jeton d'accès n'a pas de date d'expiration. Il fonctionne jusqu'à l'un de ces événements :
- Le marchand désinstalle l'application. Le jeton est révoqué et les appels répondent
401avec le codeunauthorizedet le messageInvalid or revoked API key. Un endpoint webhook vérifié reçoit un seul événementapp.uninstalled. - Vous relancez le flux d'installation pour la même boutique. Le nouveau jeton remplace l'ancien.
Tant que l'installation existe, l'API refuse le jeton avec 403 dans ces cas :
error.code | Message | Quand |
|---|---|---|
app_uninstalled | This app is no longer installed on this store | L'installation n'est plus active. |
app_suspended | This app has been suspended by DZBuild | DZBuild a suspendu ou refusé l'application. Les appels refonctionnent quand la suspension est levée. |
app_not_approved | This app is in test mode and only runs on its developer's stores | L'application n'a encore jamais été approuvée (brouillon ou première vérification) et la boutique n'appartient pas à son développeur. |
app_plan_required | This app requires the ... plan | Le plan de la boutique, ou un plan payant expiré, est inférieur au plan minimum de l'application. Le message nomme le plan. |
Via api.dzbuild.app, la passerelle transmet la réponse de DZBuild telle quelle : les codes 403 ci-dessus, un 401 ou un 429 vous arrivent avec le statut et le corps décrits ici. Si la passerelle ne peut pas vérifier le jeton auprès de DZBuild, parce que DZBuild est injoignable ou répond par une erreur serveur, elle répond 502 avec le code server_error et le message Key lookup failed, retry shortly. Réessayez après un court délai.
Il n'existe pas d'endpoint de révocation pour les applications dans la v1. La désinstallation se fait par le marchand, depuis la page de l'application dans son tableau de bord.
Renouveler le client secret dans la console développeur le remplace immédiatement. Les jetons déjà émis continuent de fonctionner.
Limites de requêtes
| Endpoint | Limite |
|---|---|
GET /oauth/apps/authorize | 30 requêtes par 300 secondes |
POST /oauth/apps/approve | 10 requêtes par 600 secondes |
POST /oauth/apps/token | 60 requêtes par 300 secondes |
| API REST avec un jeton d'application | 120 requêtes par minute et par installation, avant la limite partagée de la boutique |
Les limites OAuth se comptent par client, identifié par son adresse IP, les en-têtes du navigateur et la session. Restez en dessous : un client qui dépasse une limite peut recevoir 429 avec un en-tête Retry-After de 120 secondes au plus. Envoyez Accept: application/json sur les appels d'échange pour que le refus revienne en JSON et non en redirection. Les limites de l'API sont sur la page limites de requêtes.