TL;DR
L'API Etsy permet d'automatiser la gestion des boutiques, produits, commandes et stocks via OAuth 2.0 et des endpoints RESTful, avec une limite de 10 appels/seconde/app. Ce guide propose un parcours complet : authentification, endpoints clés, webhooks, gestion de la production et résolution des problèmes.
Essayez Apidog dès aujourd'hui
Introduction
Etsy gère plus de 13 milliards de dollars de ventes annuelles dans plus de 230 pays. Pour les développeurs d'outils e-commerce, d'inventaire ou d'analytique, intégrer l'API Etsy est essentiel.
Les vendeurs multicanal perdent 15 à 20 heures/semaine en saisie manuelle de données. L'intégration API automatise la synchronisation produits, le traitement des commandes et la gestion des stocks sur toutes les plateformes.
Ce guide détaille chaque étape d'une intégration Etsy robuste : OAuth 2.0, gestion de boutique, gestion des fiches produits, traitement des commandes, webhooks et résolution d'erreurs. À la fin, vous aurez une intégration prête pour la production.
💡 Apidog facilite les tests d'intégration API : testez vos endpoints Etsy, validez l'OAuth, inspectez les payloads webhooks et déboguez l'authentification dans un seul espace de travail. Importez des specs API, simulez des réponses et partagez vos scénarios de test avec l'équipe.
Qu'est-ce que l'API Etsy ?
Etsy fournit une API RESTful pour automatiser :
- Récupération d'infos boutique/profil
- Création/mise à jour/gestion stock de fiches produits
- Traitement des commandes & suivi logistique
- Accès aux données clients et transactions
- Profils d'expédition & calculs de taxes
- Upload d'images et médias
Fonctionnalités clés
| Fonctionnalité | Description |
|---|---|
| RESTful | Méthodes HTTP standard, réponses JSON |
| OAuth 2.0 | Authentification sécurisée, rafraîchissement des tokens |
| Webhooks | Notifications temps réel (commandes, fiches produits) |
| Limitation débit | 10 requêtes/s/app (rafale autorisée) |
| Sandbox | Environnement de test sans données réelles |
Aperçu architecture de l'API
https://openapi.etsy.com/v3/application/
Utilisez la version 3 (v3) pour toutes nouvelles intégrations (OAuth 2.0, endpoints simplifiés). Migrez rapidement vos intégrations v2, dépréciées à fin 2026.
Premiers pas : Configuration de l'authentification
Étape 1 : Créer un compte développeur Etsy
- Rendez-vous sur le Portail développeurs Etsy
- Connectez-vous ou créez un compte Etsy
- Accédez à Vos applications dans le dashboard
- Cliquez sur Créer une nouvelle application
Étape 2 : Enregistrer votre application
Remplissez le formulaire :
- Nom : descriptif, visible lors d'OAuth
- Description : ce que fait l'app, public visé
- URI de redirection : URL HTTPS de retour OAuth
- Production/Développement : commencer en développement
Après validation :
- Key String : identifiant API public
- Shared Secret : secret privé (gardez-le sécurisé)
Stockez ces infos dans un fichier .env :
ETSY_KEY_STRING="votre_chaine_de_cle_ici"
ETSY_SHARED_SECRET="votre_secret_partage_ici"
ETSY_ACCESS_TOKEN="genere_via_oauth"
ETSY_REFRESH_TOKEN="genere_via_oauth"
Étape 3 : Comprendre le flux OAuth 2.0
Flux standard :
1. L'utilisateur clique "Se connecter avec Etsy"
2. Redirection vers l'URL d'autorisation Etsy
3. Connexion & consentement utilisateur
4. Etsy redirige vers votre app avec le code d'autorisation
5. Votre app échange le code contre un access token
6. Votre app appelle l'API avec ce token
7. Rafraîchissez le token à expiration (1h)
Étape 4 : Implémenter l'autorisation OAuth
Générez l'URL d'autorisation :
const generateAuthUrl = (clientId, redirectUri, state) => {
const baseUrl = 'https://www.etsy.com/oauth/connect';
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: 'listings_r listings_w orders_r orders_w shops_r',
state: state, // Protection CSRF
response_type: 'code'
});
return `${baseUrl}?${params.toString()}`;
};
// Exemple d'utilisation
const authUrl = generateAuthUrl(
process.env.ETSY_KEY_STRING,
'https://votre-app.com/callback',
crypto.randomBytes(16).toString('hex')
);
console.log(`Rediriger l'utilisateur vers : ${authUrl}`);
Portées requises
Demandez uniquement les scopes nécessaires :
| Portée | Description | Cas d'utilisation |
|---|---|---|
listings_r |
Lire fiches produits | Affichage, synchronisation stocks |
listings_w |
Écrire fiches produits | Création/màj produits |
orders_r |
Lire commandes | Gestion/exécution |
orders_w |
Modifier commandes | Statut, ajout suivi |
shops_r |
Lire infos boutique | Profil, analytique |
transactions_r |
Lire transactions | Rapports financiers |
email |
Accès e-mail acheteur | Notifications clients |
Étape 5 : Échanger le code contre un access token
Gérez le callback OAuth :
const exchangeCodeForToken = async (code, redirectUri) => {
const response = await fetch('https://api.etsy.com/v3/public/oauth/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.ETSY_KEY_STRING,
client_secret: process.env.ETSY_SHARED_SECRET,
redirect_uri: redirectUri,
code: code
})
});
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in,
user_id: data.user_id,
scope: data.scope
};
};
// Route de callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.oauthState) {
return res.status(400).send('Paramètre d\'état invalide');
}
try {
const tokens = await exchangeCodeForToken(code, 'https://votre-app.com/callback');
await db.users.update(req.session.userId, {
etsy_access_token: tokens.access_token,
etsy_refresh_token: tokens.refresh_token,
etsy_token_expires: Date.now() + (tokens.expires_in * 1000),
etsy_user_id: tokens.user_id
});
res.redirect('/tableau-de-bord');
} catch (error) {
console.error('Échange de jetons échoué :', error);
res.status(500).send('Authentification échouée');
}
});
Étape 6 : Rafraîchir automatiquement les tokens
Les tokens expirent après 1h. Implémentez le rafraîchissement :
const refreshAccessToken = async (refreshToken) => {
const response = await fetch('https://api.etsy.com/v3/public/oauth/token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: process.env.ETSY_KEY_STRING,
client_secret: process.env.ETSY_SHARED_SECRET,
refresh_token: refreshToken
})
});
const data = await response.json();
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in
};
};
const ensureValidToken = async (userId) => {
const user = await db.users.findById(userId);
if (user.etsy_token_expires < Date.now() + 300000) {
const newTokens = await refreshAccessToken(user.etsy_refresh_token);
await db.users.update(userId, {
etsy_access_token: newTokens.access_token,
etsy_refresh_token: newTokens.refresh_token,
etsy_token_expires: Date.now() + (newTokens.expires_in * 1000)
});
return newTokens.access_token;
}
return user.etsy_access_token;
};
Étape 7 : Effectuer des appels authentifiés
Incluez le token dans chaque requête Etsy :
const makeEtsyRequest = async (endpoint, options = {}) => {
const accessToken = await ensureValidToken(options.userId);
const response = await fetch(`https://openapi.etsy.com/v3/application${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
'x-api-key': process.env.ETSY_KEY_STRING,
'Accept': 'application/json',
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Erreur API Etsy : ${error.message}`);
}
return response.json();
};
Points d'accès pour la gestion de la boutique
Récupérer les informations boutique
const getShopInfo = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}`, { method: 'GET' });
return response;
};
Réponse :
{
"shop_id": 12345678,
"shop_name": "MyHandmadeShop",
"title": "Bijoux et accessoires faits main",
...
}
Récupérer les sections de la boutique
const getShopSections = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}/sections`, { method: 'GET' });
return response;
};
Gestion des fiches produits
Créer une fiche produit
const createListing = async (shopId, listingData) => {
const payload = {
title: listingData.title,
description: listingData.description,
price: listingData.price.toString(),
quantity: listingData.quantity,
sku: listingData.sku || [],
tags: listingData.tags.slice(0, 13),
category_id: listingData.categoryId,
shop_section_id: listingData.sectionId,
state: listingData.state || 'active',
who_made: listingData.whoMade,
when_made: listingData.whenMade,
is_supply: listingData.isSupply,
item_weight: listingData.weight || null,
item_weight_unit: listingData.weightUnit || 'g',
item_length: listingData.length || null,
item_width: listingData.width || null,
item_height: listingData.height || null,
item_dimensions_unit: listingData.dimensionsUnit || 'mm',
is_private: listingData.isPrivate || false,
recipient: listingData.recipient || null,
occasion: listingData.occasion || null,
style: listingData.style || []
};
const response = await makeEtsyRequest(`/shops/${shopId}/listings`, {
method: 'POST',
body: JSON.stringify(payload)
});
return response;
};
Upload d'images pour fiche produit
const uploadListingImage = async (listingId, imagePath, imagePosition = 1) => {
const fs = require('fs');
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');
const payload = {
image: base64Image,
listing_image_id: null,
position: imagePosition,
is_watermarked: false,
alt_text: 'Collier phase de lune en argent sterling fait main'
};
const response = await makeEtsyRequest(`/listings/${listingId}/images`, {
method: 'POST',
body: JSON.stringify(payload)
});
return response;
};
Mettre à jour l'inventaire
const updateListingInventory = async (shopId, listingId, inventory) => {
const payload = {
products: inventory.products.map(product => ({
sku: product.sku,
quantity: product.quantity
})),
is_over_selling: inventory.isOverSelling || false,
on_property: inventory.onProperty || []
};
const response = await makeEtsyRequest(
`/shops/${shopId}/listings/${listingId}/inventory`,
{
method: 'PUT',
body: JSON.stringify(payload)
}
);
return response;
};
Lire, supprimer, filtrer les fiches produits
const getListings = async (shopId, options = {}) => {
const params = new URLSearchParams({
limit: options.limit || 25,
offset: options.offset || 0
});
if (options.state) params.append('state', options.state);
const response = await makeEtsyRequest(
`/shops/${shopId}/listings?${params.toString()}`,
{ method: 'GET' }
);
return response;
};
const deleteListing = async (listingId) => {
return await makeEtsyRequest(`/listings/${listingId}`, { method: 'DELETE' });
};
Gestion des commandes
Récupérer et filtrer les commandes
const getOrders = async (shopId, options = {}) => {
const params = new URLSearchParams({
limit: options.limit || 25,
offset: options.offset || 0
});
if (options.status) params.append('status', options.status);
if (options.minLastModified) params.append('min_last_modified', options.minLastModified);
const response = await makeEtsyRequest(
`/shops/${shopId}/orders?${params.toString()}`,
{ method: 'GET' }
);
return response;
};
Mettre à jour le statut de la commande
const updateOrderStatus = async (shopId, orderId, trackingData) => {
const payload = {
carrier_id: trackingData.carrierId,
tracking_code: trackingData.trackingCode,
should_send_bcc_to_buyer: trackingData.notifyBuyer || true
};
return await makeEtsyRequest(
`/shops/${shopId}/orders/${orderId}/shipping`,
{
method: 'POST',
body: JSON.stringify(payload)
}
);
};
ID transporteurs courants
| Transporteur | ID |
|---|---|
| USPS | usps |
| FedEx | fedex |
| UPS | ups |
| DHL | dhl_express |
| Postes Canada | canada_post |
| Royal Mail | royal_mail |
| Australia Post | australia_post |
Limitation de débit et quotas
Limites Etsy
- Standard : 10 requêtes/seconde/app
- Rafale : jusqu'à 50 en une seconde
- Quota : 10 000 appels/heure/app
HTTP 429 si dépassement.
Gestion de la limitation
Attente exponentielle :
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await makeEtsyRequest(endpoint, options);
// Surveillez les headers de quota pour adapter le rythme
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
Surveillez les headers :
| En-tête | Description |
|---|---|
x-etsy-quota-remaining |
Appels restants (heure) |
x-etsy-quota-reset |
Réinitialisation quota (epoch) |
x-etsy-limit-remaining |
Appels restants (seconde) |
x-etsy-limit-reset |
Réinitialisation limite |
Intégration des webhooks
Configuration
- Allez dans Vos applications sur le dashboard Etsy
- Sélectionnez l'application
- Cliquez Ajouter un Webhook
- Renseignez votre endpoint HTTPS
- Choisissez les événements souhaités
| Événement | Déclencheur | Cas d'utilisation |
|---|---|---|
v3/shops/{shop_id}/orders/create |
Nouvelle commande | Confirmation, exécution |
v3/shops/{shop_id}/orders/update |
Statut changé | Sync statut |
v3/shops/{shop_id}/listings/create |
Nouvelle fiche | Sync inventaire |
v3/shops/{shop_id}/listings/update |
Fiche modifiée | Sync produit |
v3/shops/{shop_id}/listings/delete |
Fiche supprimée | Nettoyage externe |
Implémenter un handler webhook
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/etsy', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-etsy-signature'];
const payload = req.body;
const isValid = verifyWebhookSignature(payload, signature, process.env.ETSY_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Non autorisé');
}
const event = JSON.parse(payload.toString());
switch (event.type) {
case 'v3/shops/*/orders/create':
await handleNewOrder(event.data);
break;
// autres cas...
default:
console.log('Type d\'événement non géré :', event.type);
}
res.status(200).send('OK');
});
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
Bonnes pratiques webhooks
- Vérifiez la signature systématiquement
- Répondez 200 OK sous 5s (sinon Etsy retente)
- Traitez en async (file d'attente)
- Implémentez l'idempotence (événements en double)
- Logguez tous les événements pour debug
Dépannage des problèmes courants
Échec échange de jetons OAuth (401/403)
- Vérifiez URI de redirection exacte (HTTPS, slash final)
- Vérifiez
client_idetclient_secret - Le code d'autorisation expire après 1 utilisation/5min
- App en mode développement : accès limité
Limite de débit dépassée (429)
- Implémentez file d'attente + rate limiting
- Attente exponentielle sur nouvelle tentative
- Groupez les requêtes si possible
- Surveillez les headers de quota
Rate limiter simple :
class RateLimiter {
constructor(requestsPerSecond = 9) {
this.queue = [];
this.interval = 1000 / requestsPerSecond;
this.processing = false;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
if (this.queue.length > 0) {
await new Promise(r => setTimeout(r, this.interval));
}
}
this.processing = false;
}
}
// Utilisation
const etsyRateLimiter = new RateLimiter(9);
const result = await etsyRateLimiter.add(() => makeEtsyRequest('/shops/12345/listings'));
Erreur validation fiche produit (400)
-
category_idinvalide : vérifiez via l'API catégories -
pricedoit être une chaîne - Max 13 tags/fiches
- Champs obligatoires : title, description, price, quantity, who_made, when_made
const validateListing = (data) => {
const errors = [];
if (!data.title || data.title.length < 5) errors.push('Le titre doit contenir au moins 5 caractères');
if (typeof data.price !== 'string') errors.push('Le prix doit être une chaîne');
if (data.tags && data.tags.length > 13) errors.push('Maximum 13 tags autorisés');
if (!['i_did', 'someone_else', 'collective'].includes(data.whoMade)) errors.push('Valeur who_made invalide');
return errors;
};
Webhooks non reçus
- Vérifiez les logs de livraison webhooks Etsy
- Vérifiez que le endpoint répond 200 OK < 5s
- Testez manuellement avec
curl - Assurez HTTPS + certificat SSL valide
- Liste blanche des IPs Etsy dans le firewall
- Vérifiez la logique de signature
Problème upload images
- Format valide (JPEG, PNG, GIF)
- Taille < 20 Mo/image
- Encodage base64 correct
- Fiche produit existante
- Uploader les images séquentiellement
Liste de contrôle de production
- [ ] Passez en mode production
- [ ] Mettez à jour les URI de redirection
- [ ] Stockage sécurisé des tokens (DB chiffrée)
- [ ] Rafraîchissement automatique des tokens
- [ ] Rate limiting & file d'attente des requêtes
- [ ] Webhook HTTPS configuré
- [ ] Gestion complète des erreurs
- [ ] Journalisation des appels API
- [ ] Surveillance de quota
- [ ] Manuel d'exploitation
- [ ] Tests multi-comptes boutiques
- [ ] Documentation du flow OAuth
Surveillance & alertes
const metrics = {
apiCalls: { total: 0, successful: 0, failed: 0, rateLimited: 0 },
quotaUsage: { current: 0, limit: 10000, resetTime: null },
oauthTokens: { active: 0, expiring_soon: 0, refresh_failures: 0 },
webhooks: { received: 0, processed: 0, failed: 0 }
};
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) { sendAlert('Taux d\'échec de l\'API Etsy supérieur à 5%'); }
if (metrics.quotaUsage.current < 500) { sendAlert('Quota API Etsy inférieur à 500 appels restants'); }
Cas d'utilisation réels
Synchronisation des stocks multicanal
- Défi : Stocks manuels, surventes
- Solution : Système centralisé + webhooks Etsy
- Résultat : Zéro survente, 12h/semaine gagnées
Implémentation :
- Webhook Etsy déclenche à la création commande
- Inventaire central décrémente produit
- Màj stocks Shopify/Amazon via API
- Confirmation stockée
Exécution automatisée des commandes
- Défi : 50+ commandes/jour, saisie manuelle
- Solution : Intégration API Etsy + fournisseur d'exécution
- Résultat : Commandes transmises en <5min
Tableau de bord analytique
- Défi : Rapports éclatés multi-boutiques
- Solution : Agrégation des données via OAuth
- Résultat : Dashboard temps réel (ventes, trafic, conversion)
Conclusion
- OAuth 2.0 demande une gestion attentive des tokens et leur rafraîchissement automatique
- Le rate limiting (10/s, 10K/h) impose file d'attente & surveillance proactive
- Les webhooks permettent la synchronisation temps réel
- La gestion d'erreur et retry est cruciale en production
- Apidog simplifie les tests API et la collaboration sur les intégrations Etsy
Section FAQ
À quoi sert l'API Etsy ?
L'API Etsy permet de développer des applications pour la gestion multi-canal des stocks, l'automatisation des commandes, la création de fiches produits, l'analytique et le CRM.
Comment obtenir une clé API Etsy ?
Créez un compte sur le portail développeurs Etsy, puis dans Vos applications, cliquez sur Créer une nouvelle application. Stockez la clé publique et le secret en variables d'environnement.
L'API Etsy est-elle gratuite ?
Oui, dans la limite de 10 requêtes/s et 10 000 appels/h/app. Plus de quota ? Demander une extension à Etsy.
Quelle authentification utilise l’API Etsy ?
OAuth 2.0. L'utilisateur autorise l'app, qui reçoit un access token (1h) + refresh token.
Comment gérer les limites de débit Etsy ?
File d'attente de requêtes, respecter les headers x-etsy-quota-remaining. Attente exponentielle sur 429.
Peut-on tester l’API sans boutique réelle ?
Oui, en mode développement sur une boutique de test.
Comment fonctionnent les webhooks Etsy ?
Etsy POST des événements à votre endpoint HTTPS à chaque action (commande, fiche produit). Vérifiez la signature, répondez 200 OK sous 5 s.
Que se passe-t-il quand le token OAuth expire ?
Il expire après 1h. Utilisez le refresh token pour en obtenir un nouveau et rafraîchissez automatiquement côté serveur.
Peut-on uploader des images via l’API ?
Oui, après la création de la fiche produit, via POST base64 (max 20 Mo, JPEG/PNG/GIF).
Comment migrer de l’API Etsy V2 vers V3 ?
Passez à OAuth 2.0, adaptez les endpoints (/v3/application/), testez à fond avant la fin de V2 (2026).
Top comments (0)