TL;DR
Die Etsy API ermöglicht es Entwicklern, Anwendungen zu bauen, die direkt mit dem Etsy-Marktplatz interagieren. Sie verwendet OAuth 2.0, bietet RESTful-Endpunkte für Shops, Listings, Bestellungen und Inventar, und limitiert Anfragen auf 10 pro Sekunde pro App. In diesem Leitfaden findest du praxisorientierte Schritte zur Authentifizierung, Nutzung zentraler Endpunkte, Webhook-Integration und Produktions-Bereitstellung.
Einleitung
Etsy verarbeitet jährlich Bruttowarenverkäufe von über 13 Milliarden US-Dollar in 230+ Ländern. Für Entwickler von E-Commerce-Tools, Inventarsystemen oder Analyseplattformen ist die Etsy API-Integration unverzichtbar.
Viele Verkäufer verlieren 15–20 Stunden pro Woche durch manuelle Dateneingabe bei Multichannel-Vertrieb. Eine robuste Etsy API-Integration automatisiert Angebotssynchronisierung, Bestellabwicklung und Inventar-Updates.
In diesem Leitfaden implementierst du Schritt für Schritt eine produktionsreife Etsy API-Integration: von OAuth 2.0 über Shop-/Listing-Management und Bestellverarbeitung bis zu Webhook-Handling, Fehlerbehebung und Deployment-Strategien.
💡 Apidog vereinfacht das Testen deiner API-Integrationen: Teste Etsy-Endpunkte, prüfe OAuth-Flows, inspiziere Webhook-Payloads und debugge Auth-Probleme zentral. Importiere API-Spezifikationen, modelliere Antworten und teile Testszenarien mit deinem Team.
Was ist die Etsy API?
Etsy stellt eine RESTful API bereit, um auf Marktplatzdaten zuzugreifen und Verkäuferoperationen zu steuern:
- Shop- und Profilinformationen abrufen
- Angebote erstellen/aktualisieren, Bestandsverwaltung
- Auftragsabwicklung & Fulfillment
- Kunden- und Transaktionsdaten abrufen
- Versandprofile, Steuerberechnung
- Bild-/Medien-Uploads verwalten
Hauptmerkmale
| Merkmal | Beschreibung |
|---|---|
| RESTful Design | HTTP-Methoden, JSON-Responses |
| OAuth 2.0 | Sichere Authentifizierung, Token-Refresh |
| Webhooks | Echtzeitbenachrichtigungen zu Orders/Listings |
| Ratenbegrenzung | 10 Requests/s/App (mit Burst-Kontingent) |
| Sandbox | Entwickeln & testen ohne Live-Daten |
API-Architektur
API-Basis (aktuell v3):
https://openapi.etsy.com/v3/application/
Nutze v3 für alle neuen Projekte. Etsy stellt v2 zum 2026 ein.
| Version | Status | Auth | Anwendungsfall |
|---|---|---|---|
| V3 | Aktuell | OAuth 2.0 | Neue Integrationen |
| V2 | Veraltet | OAuth 1.0a | Legacy |
| V1 | Eingestellt | N/A | Nicht verwenden |
Wichtig: Migriere bestehende V2-Integrationen sofort auf v3.
Erste Schritte: Authentifizierung
1. Etsy-Entwicklerkonto anlegen
- Etsy Developer Portal öffnen
- Mit Etsy-Account einloggen (oder neuen erstellen)
- Im Dashboard zu Ihre Apps navigieren
- Neue App erstellen anklicken
2. Anwendung registrieren
- App-Name: Klar und verständlich (wird im OAuth-Dialog angezeigt)
- Beschreibung: Was macht die App? Wer nutzt sie?
- Redirect-URI: Ziel-URL nach Auth (HTTPS erforderlich)
- Modus: Mit Entwicklungsmodus starten
Nach Registrierung erhältst du:
- Key String (öffentlich)
- Shared Secret (privat, nie öffentlich!)
Speichere Credentials sicher:
# .env
ETSY_KEY_STRING="dein_key"
ETSY_SHARED_SECRET="dein_secret"
ETSY_ACCESS_TOKEN="via_oauth_gen"
ETSY_REFRESH_TOKEN="via_oauth_gen"
3. OAuth 2.0 Flow verstehen
- User klickt „Mit Etsy verbinden“
- App leitet zu Etsy-Auth-URL weiter
- User loggt sich ein, gibt Rechte frei
- Etsy schickt Auth-Code an Redirect-URI
- App tauscht Code gegen Access Token
- Mit Access Token API-Calls durchführen
- Token regelmäßig mit Refresh Token erneuern
4. OAuth-Autorisierung implementieren
Generiere die OAuth-Autorisierungs-URL:
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, // Zufallswert für CSRF-Schutz
response_type: 'code'
});
return `${baseUrl}?${params.toString()}`;
};
// Beispiel
const authUrl = generateAuthUrl(
process.env.ETSY_KEY_STRING,
'https://deine-app.com/callback',
crypto.randomBytes(16).toString('hex')
);
console.log(`Benutzer weiterleiten zu: ${authUrl}`);
Scopes
Fordere nur die Rechte an, die du wirklich brauchst:
| Scope | Beschreibung | Use Case |
|---|---|---|
listings_r |
Listings lesen | Produkte anzeigen |
listings_w |
Listings schreiben | Produkte erstellen |
orders_r |
Bestellungen lesen | Order-Management |
orders_w |
Bestellungen schreiben | Status, Tracking setzen |
shops_r |
Shopinfo lesen | Profil, Analytics |
transactions_r |
Transaktionen lesen | Finanzberichte |
email |
Käufer-Mail lesen | Bestellkommunikation |
5. Code gegen Access Token eintauschen
Verarbeite den OAuth-Callback:
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();
// Sicher speichern!
return {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in,
user_id: data.user_id,
scope: data.scope
};
};
// Express-Callback-Route
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.oauthState) {
return res.status(400).send('Ungültiger State');
}
try {
const tokens = await exchangeCodeForToken(code, 'https://deine-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('/dashboard');
} catch (error) {
console.error('Token-Austausch fehlgeschlagen:', error);
res.status(500).send('Authentifizierung fehlgeschlagen');
}
});
6. Token-Refresh implementieren
Access Tokens laufen nach 1 Stunde ab – automatisiere das Refreshing:
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
};
};
// Vor API-Call Token prüfen
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;
};
7. Authentifizierte API-Requests
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(`Etsy API Fehler: ${error.message}`);
}
return response.json();
};
Shop-Management Endpunkte
Shop-Informationen abrufen
const getShopInfo = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}`, { method: 'GET' });
return response;
};
// Beispiel
const shop = await getShopInfo(12345678);
console.log(`Shop: ${shop.title}`);
console.log(`Währung: ${shop.currency_code}`);
console.log(`Aktive Angebote: ${shop.num_listings_active}`);
Shop-Kategorien abrufen
const getShopSections = async (shopId) => {
const response = await makeEtsyRequest(`/shops/${shopId}/sections`, {
method: 'GET'
});
return response;
};
Angebotsverwaltung
Neues Angebot erstellen
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;
};
// Beispiel
const listing = await createListing(12345678, {
title: 'Halskette mit Mondphasen aus Sterlingsilber',
description: 'Handgefertigte Halskette aus Sterlingsilber mit Mondphasen...',
price: 89.99,
quantity: 15,
sku: ['MOON-NECKLACE-001'],
tags: ['Mondkette', 'Sterlingsilber', 'Mondphase', 'himmlischer Schmuck'],
categoryId: 10623,
sectionId: 12345,
state: 'active',
whoMade: 'i_did',
whenMade: 'made_to_order',
isSupply: false,
weight: 25,
weightUnit: 'g'
});
Angebotsbilder hochladen
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: 'Handgefertigte Halskette mit Mondphasen aus Sterlingsilber'
};
const response = await makeEtsyRequest(`/listings/${listingId}/images`, {
method: 'POST',
body: JSON.stringify(payload)
});
return response;
};
Bestellverwaltung
Bestellungen abrufen
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;
};
Bestellstatus aktualisieren
const updateOrderStatus = async (shopId, orderId, trackingData) => {
const payload = {
carrier_id: trackingData.carrierId,
tracking_code: trackingData.trackingCode,
should_send_bcc_to_buyer: trackingData.notifyBuyer || true
};
const response = await makeEtsyRequest(
`/shops/${shopId}/orders/${orderId}/shipping`,
{
method: 'POST',
body: JSON.stringify(payload)
}
);
return response;
};
Ratenbegrenzung & Quotas
Ratenbegrenzungen umsetzen
- Standard: 10 Requests/s/App
- Quota: 10.000/h/App (stündlich zurückgesetzt)
- Überschreitung → HTTP 429
Exponentielles Backoff implementieren:
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await makeEtsyRequest(endpoint, options);
// ... Header prüfen ...
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;
}
}
}
};
Webhook-Integration
Webhooks konfigurieren
- Im Developer Dashboard unter Ihre Apps die App auswählen
- Webhook hinzufügen
- HTTPS-Endpoint eingeben
- Zu abonnierende Events auswählen
Webhook-Handler (Node.js/Express)
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('Nicht autorisiert');
}
const event = JSON.parse(payload.toString());
switch (event.type) {
case 'v3/shops/*/orders/create':
await handleNewOrder(event.data);
break;
// Weitere Events ...
default:
console.log('Unbehandelter Ereignistyp:', 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')
);
}
Best Practices:
- Signatur prüfen
- Immer schnell mit 200 OK antworten
- Events asynchron verarbeiten
- Idempotenz sicherstellen
- Audit-Log der Events führen
Fehlerbehebung
Problem: OAuth-Token-Austausch schlägt fehl
- 401/403 Fehler: Prüfe Redirect-URI (genauer String inkl. https://), client_id/client_secret, Gültigkeit des Codes, App-Modus
- Fehlerausgabe prüfen:
const error = await response.json();
console.error('OAuth-Fehler:', error);
Problem: Ratenlimit überschritten
- Anfragewarteschlange & Rate Limiter nutzen, Backoff bei HTTP 429, Quota-Header überwachen
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;
}
}
const etsyRateLimiter = new RateLimiter(9);
const result = await etsyRateLimiter.add(() => makeEtsyRequest('/shops/12345/listings'));
Problem: Angebotserstellung scheitert mit Validierungsfehlern
- Prüfe category_id, Preis als String, max. 13 Tags, Pflichtfelder
- Beispiel-Validierung:
const validateListing = (data) => {
const errors = [];
if (!data.title || data.title.length < 5) errors.push('Titel zu kurz');
if (typeof data.price !== 'string') errors.push('Preis muss String sein');
if (data.tags && data.tags.length > 13) errors.push('Max 13 Tags');
if (!['i_did', 'someone_else', 'collective'].includes(data.whoMade))
errors.push('Ungültiger Wert für who_made');
return errors;
};
Problem: Webhooks kommen nicht an
- Prüfe Webhook-Logs im Dashboard
- Endpoint muss 200 OK in <5s liefern
- SSL & Firewall prüfen, Signatur-Logik kontrollieren
Problem: Bilder-Upload schlägt fehl
- Format: JPEG, PNG, GIF
- Max. 20 MB/Bild
- Base64-Kodierung prüfen
- Existenz des Listings sicherstellen
- Bilder nacheinander hochladen
Produktions-Checkliste
- [ ] Produktionsmodus für App aktivieren
- [ ] Redirect-URIs auf Live-URLs setzen
- [ ] Tokens verschlüsselt speichern
- [ ] Token-Refresh automatisieren
- [ ] Ratenbegrenzung & Queue implementieren
- [ ] HTTPS für Webhooks erzwingen
- [ ] Fehlerbehandlung umsetzen
- [ ] Logging aller API-Calls aktivieren
- [ ] Quota-Nutzung überwachen
- [ ] Troubleshooting-Doku erstellen
- [ ] Mit mehreren Shops testen
- [ ] OAuth-Flow für User dokumentieren
Überwachung/Alerting
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('Etsy API Fehlerquote > 5%');
}
if (metrics.quotaUsage.current < 500) {
sendAlert('Etsy API Quota < 500');
}
Praktische Anwendungsfälle
Mehrkanal-Bestandssynchronisierung
- Problem: Manuelle Updates führen zu Überverkäufen
- Lösung: Zentrales Inventar per Webhook-Trigger, Etsy API und weitere Plattformen synchronisieren
- Ablauf: Webhook → Bestand reduzieren → Mengen via API aktualisieren → Bestätigung loggen
Automatisierte Auftragsabwicklung
- Problem: 50+ Bestellungen/Tag, viel manuelle Eingabe
- Lösung: API-Integration mit Fulfillment-Anbieter & Webhook
- Ablauf: Webhook orders/create → Daten an Fulfillment-API → Trackingnummer via Etsy API → Kunde erhält Versandbenachrichtigung
Analyse-Dashboard
- Problem: Keine zentrale Auswertung für Multishop-Verkäufer
- Lösung: Datenaggregation via OAuth-Flow, Echtzeit-Dashboard
- Datenquellen: Shop-Stats, Bestellhistorie, Listings, Bewertungen
Fazit
Mit der Etsy API automatisierst und skalierst du Shop-Integrationen effizient:
- Implementiere zuverlässigen OAuth 2.0 Flow & Token-Refresh
- Beachte Ratenbegrenzung (10/s, 10.000/h) und setze Request-Queues ein
- Nutze Webhooks für Echtzeit-Synchronisierung
- Fehlerbehandlung und Monitoring sind Pflicht für Produktion
- Apidog beschleunigt Integrationstests und Zusammenarbeit im Team
FAQ-Bereich
Wofür wird die Etsy API verwendet?
Für Multichannel-Bestandssynchronisierung, automatisierte Auftragsabwicklung, Analyse-Dashboards, Angebotserstellung und CRM-Lösungen.
Wie erhalte ich einen Etsy API-Schlüssel?
Im Etsy Developer Portal App anlegen. Key String und Shared Secret sicher speichern (z.B. in .env).
Ist die Etsy API kostenlos nutzbar?
Ja, aber mit Limits: 10/s und 10.000/h pro App. Höhere Limits nur nach Antrag.
Welche Authentifizierung verwendet Etsy?
OAuth 2.0 – User autorisieren deine App, du erhältst Access- und Refresh-Token.
Wie gehe ich mit Etsy API-Ratenlimits um?
Warteschlange & Rate Limiter nutzen. Quota-Header (x-etsy-quota-remaining) monitoren. Exponentielles Backoff bei 429-Fehlern.
Kann ich die API ohne Live-Shop testen?
Ja, mit Entwicklungsmodus und Testshops.
Wie funktionieren Webhooks?
Etsy sendet POSTs an deinen HTTPS-Webhook für relevante Events (Order/Listing). Endpoint muss 200 OK in <5s liefern, Signatur prüfen.
Was passiert, wenn ein OAuth Token abläuft?
Tokens laufen nach 1h ab. Vor Ablauf automatisch mit Refresh Token erneuern.
Kann ich Angebotsbilder via API hochladen?
Ja, separat als Base64-String, max. 20 MB, JPEG/PNG/GIF.
Wie migriere ich von V2 auf V3?
OAuth 2.0 anstatt 1.0a, Endpunkte auf /v3/application/ umstellen, Auth-Flow und Tests anpassen. Bis Ende 2026 migrieren.
Top comments (0)