To integrate Replit with Etsy API, register an app on the Etsy developer portal, implement OAuth 2.0 PKCE flow from your Replit server, store credentials in Replit Secrets (lock icon 🔒), and call the Etsy Open API v3 endpoints for listings, shops, and receipts. Etsy uses OAuth 2.0 with PKCE (no client secret) for all operations that require seller authorization. Use an Autoscale deployment for the OAuth callback handler.
Etsy API Integration from Replit: Listings, Shops, and Orders
Etsy's Open API v3 is the access point for the handmade marketplace's data: shop listings with photos and pricing, order receipts, shipping profiles, and seller analytics. For app builders, the Etsy API enables custom seller dashboards, inventory sync tools, order management automation, and listing analytics beyond what Etsy's native tools provide. With millions of active sellers, Etsy integrations are in demand for e-commerce tooling.
Etsy uses OAuth 2.0 with PKCE exclusively for API v3. Unlike older OAuth 2.0 implementations with client secrets, PKCE uses a cryptographically random code verifier generated per-request — there is no static secret to store or protect. Your Replit app generates a code verifier (random string), derives a code challenge from it (SHA256 hash), sends the challenge to Etsy's auth endpoint, and then proves ownership by sending the original verifier during the token exchange. This design is specifically intended for public clients and server-side apps that cannot safely store a secret.
Public data — listing search, shop info, listing photos — can be accessed with just your API key (x-api-key header) without OAuth. This is useful for marketplace search features or displaying Etsy shop data on a website. Seller-specific operations like reading private orders, updating listings, or accessing financial data all require a user OAuth token with appropriate scopes.
Integration method
Etsy Open API v3 uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) — there is no client secret involved. Your Replit server generates a code verifier and challenge, redirects the seller to Etsy's authorization page, handles the OAuth callback to exchange the code for an access token, and stores the token for subsequent API calls. Public data (listing search, shop info) can be accessed with just an API key. Seller-specific operations (orders, inventory updates) require a user access token obtained through the PKCE flow.
Prerequisites
- A Replit account with a Node.js or Python Repl ready
- An Etsy seller account (required to access seller API features)
- An Etsy developer account at developers.etsy.com
- An Etsy app registered with OAuth redirect URI pointing to your Replit app URL
Step-by-step guide
Register an Etsy Developer App
Register an Etsy Developer App
Navigate to developers.etsy.com and sign in with your Etsy account. If you do not have a developer account, you will be prompted to create one — this requires agreeing to the developer terms of service. Click 'Create a New App' and fill in the required fields: App Name (your integration's name), App Description (what the app does), and how you plan to use the API. Under the OAuth configuration section, add a Redirect URI. This must be the URL of your Replit app's callback endpoint — for example, https://yourapp.yourusername.repl.co/auth/callback. For development, you can use your Replit dev URL. For production, use your deployed Autoscale URL or custom domain. Etsy only redirects to pre-registered redirect URIs, so you must update this when your URL changes. After creating the app, you will see your Keystring (this is your API key / client ID). The Keystring is not secret — it identifies your app. You will include it in API requests as the x-api-key header for public endpoints. Etsy Open API v3 does NOT have a client secret — the PKCE flow is used instead, so you only have a Keystring. Note your Keystring and the authorized scopes you will need. Common scopes are: listings_r (read listings), listings_w (write listings), transactions_r (read orders), shops_r (read shop data). Request only the scopes your app needs.
Pro tip: Etsy apps go through a review process before receiving production access to all API scopes. During development, your app has limited quota. Register as early as possible and apply for production access when your integration is ready, as review can take several days.
Expected result: An Etsy developer app exists with your Replit URL as the redirect URI. You have your app's Keystring (API key / client ID).
Store Etsy Credentials in Replit Secrets
Store Etsy Credentials in Replit Secrets
Click the lock icon (🔒) in the left Replit sidebar to open the Secrets pane. Add the following secrets: ETSY_CLIENT_ID: your app's Keystring (the API key from the developer portal — this identifies your app). ETSY_REDIRECT_URI: the full redirect URI you registered (e.g., https://yourapp.yourusername.repl.co/auth/callback). This must match exactly what you registered on the developer portal. ETSY_SCOPES: space-separated list of scopes your app needs (e.g., listings_r transactions_r shops_r). When you complete the OAuth flow and receive tokens, also store them in Secrets: ETSY_ACCESS_TOKEN: the OAuth access token received after PKCE authorization. ETSY_REFRESH_TOKEN: the refresh token (if your app type supports offline access). For a production multi-user app, you would store tokens in a database keyed by user ID rather than as environment variables. For a single-seller tool, storing the token in Secrets is fine. The access token expires after 3600 seconds (1 hour). Your code must handle token expiry by catching 401 responses and refreshing the token automatically.
1// check-etsy-secrets.js2const required = ['ETSY_CLIENT_ID', 'ETSY_REDIRECT_URI', 'ETSY_SCOPES'];3for (const key of required) {4 if (!process.env[key]) {5 throw new Error(`Missing secret: ${key}. Set it in Replit Secrets (lock icon 🔒).`);6 }7}8console.log('Etsy config OK.');9console.log('Client ID:', process.env.ETSY_CLIENT_ID);10console.log('Scopes:', process.env.ETSY_SCOPES);Pro tip: The ETSY_CLIENT_ID (Keystring) is not truly secret — it is sent in URLs and headers and visible to users. However, storing it in Secrets alongside sensitive tokens ensures consistent configuration management and allows you to rotate it if needed.
Expected result: ETSY_CLIENT_ID, ETSY_REDIRECT_URI, and ETSY_SCOPES appear in Replit Secrets. The check script prints the client ID and scopes without errors.
Implement OAuth 2.0 PKCE Authorization Flow (Node.js)
Implement OAuth 2.0 PKCE Authorization Flow (Node.js)
Install dependencies in the Shell tab: npm install axios express crypto-js. The PKCE flow has three steps: generate a code verifier + challenge, redirect the user to Etsy's authorization page, and exchange the returned code for access tokens. Step 1 — Generate PKCE values: create a random 43-128 character code verifier (alphanumeric + -._~), then generate a code challenge by taking the SHA256 hash of the verifier and base64url-encoding the result. Store the code verifier in the session (or a server-side cache) — you will need it in step 3. Step 2 — Authorization redirect: build the Etsy authorization URL with query parameters: client_id (your Keystring), redirect_uri, response_type=code, scope (space-separated), state (random nonce for CSRF protection), and code_challenge + code_challenge_method=S256. Redirect the user to this URL. Step 3 — Token exchange: when Etsy redirects to your callback with a code parameter, call Etsy's token endpoint (POST https://api.etsy.com/v3/public/oauth/token) with grant_type=authorization_code, client_id, redirect_uri, code, and code_verifier. Etsy returns an access_token and refresh_token. Store these securely. For public data endpoints (no OAuth required), include x-api-key: {ETSY_CLIENT_ID} in request headers. For seller endpoints, include Authorization: Bearer {access_token} instead.
1// etsy.js — Etsy OAuth 2.0 PKCE + API integration for Node.js on Replit2const express = require('express');3const axios = require('axios');4const crypto = require('crypto');56const app = express();7app.use(express.json());89const CLIENT_ID = process.env.ETSY_CLIENT_ID;10const REDIRECT_URI = process.env.ETSY_REDIRECT_URI;11const SCOPES = process.env.ETSY_SCOPES || 'listings_r transactions_r shops_r';1213// In-memory store for PKCE verifiers (use Redis/DB in production)14const pkceStore = {};1516function generateCodeVerifier() {17 return crypto.randomBytes(32).toString('base64url');18}1920function generateCodeChallenge(verifier) {21 return crypto.createHash('sha256').update(verifier).digest('base64url');22}2324// Step 1: Start OAuth — generate PKCE and redirect to Etsy25app.get('/auth/login', (req, res) => {26 const codeVerifier = generateCodeVerifier();27 const codeChallenge = generateCodeChallenge(codeVerifier);28 const state = crypto.randomBytes(16).toString('hex');29 30 // Store verifier for later (use session store in production)31 pkceStore[state] = codeVerifier;32 33 const params = new URLSearchParams({34 response_type: 'code',35 redirect_uri: REDIRECT_URI,36 scope: SCOPES,37 client_id: CLIENT_ID,38 state,39 code_challenge: codeChallenge,40 code_challenge_method: 'S256'41 });42 43 res.redirect(`https://www.etsy.com/oauth/connect?${params}`);44});4546// Step 2: OAuth callback — exchange code for tokens47app.get('/auth/callback', async (req, res) => {48 const { code, state } = req.query;49 const codeVerifier = pkceStore[state];50 51 if (!codeVerifier) return res.status(400).send('Invalid state parameter');52 delete pkceStore[state];53 54 try {55 const tokenResponse = await axios.post('https://api.etsy.com/v3/public/oauth/token', {56 grant_type: 'authorization_code',57 client_id: CLIENT_ID,58 redirect_uri: REDIRECT_URI,59 code,60 code_verifier: codeVerifier61 });62 63 const { access_token, refresh_token, expires_in } = tokenResponse.data;64 // In production: store tokens securely in database65 // For single-user tool: update ETSY_ACCESS_TOKEN in Secrets manually66 console.log('OAuth complete. Access token expires in:', expires_in, 'seconds');67 res.json({ success: true, expires_in });68 } catch (err) {69 res.status(500).json({ error: err.response?.data || err.message });70 }71});7273// Get current user's shop info (requires OAuth token)74app.get('/api/shop', async (req, res) => {75 const token = process.env.ETSY_ACCESS_TOKEN;76 try {77 const response = await axios.get('https://openapi.etsy.com/v3/application/users/me/shops', {78 headers: { 'Authorization': `Bearer ${token}`, 'x-api-key': CLIENT_ID }79 });80 res.json(response.data);81 } catch (err) {82 res.status(err.response?.status || 500).json({ error: err.response?.data || err.message });83 }84});8586// Search public listings by keyword (API key only, no OAuth needed)87app.get('/api/listings/search', async (req, res) => {88 const { keywords, limit = 25 } = req.query;89 try {90 const response = await axios.get('https://openapi.etsy.com/v3/application/listings/active', {91 headers: { 'x-api-key': CLIENT_ID },92 params: { keywords, limit }93 });94 res.json(response.data);95 } catch (err) {96 res.status(err.response?.status || 500).json({ error: err.response?.data || err.message });97 }98});99100app.listen(3000, '0.0.0.0', () => console.log('Etsy server running on port 3000'));Pro tip: The pkceStore in the example uses an in-memory object. In production or multi-instance deployments, store PKCE verifiers in a database or Redis keyed by the state parameter. Memory-stored verifiers are lost on server restart.
Expected result: Navigating to /auth/login redirects to Etsy's authorization page. After the seller authorizes, /auth/callback exchanges the code for tokens. GET /api/listings/search returns Etsy listings matching the keyword.
Python Integration for Etsy API
Python Integration for Etsy API
For Python Replit projects, install requests and flask: pip install requests flask. The PKCE flow works the same way as Node.js — generate the code verifier and challenge using Python's hashlib and base64 modules, redirect to Etsy's authorization URL, and exchange the code at the token endpoint. Python's standard library has all the cryptographic primitives you need: secrets.token_urlsafe() for the code verifier, hashlib.sha256() for the challenge, and base64.urlsafe_b64encode() for base64url encoding. Note that base64url encoding must not have padding characters — use .rstrip('=') to remove them. For Etsy API calls after OAuth, use requests.Session() with the Authorization header set. The access token expires in 3600 seconds. Implement a refresh token flow: when you receive a 401 response, call the token endpoint with grant_type=refresh_token, your CLIENT_ID, and the stored refresh_token to get a new access token. For a single-seller tool deployed on Replit, run the OAuth flow once, copy the access_token and refresh_token from the logs or response, and store them in Replit Secrets. Add automatic refresh logic so the token renews without manual intervention.
1# etsy_api.py — Etsy Open API v3 with OAuth 2.0 PKCE for Python on Replit2import os3import hashlib4import base645import secrets6import requests7from flask import Flask, request, redirect, jsonify, session89app = Flask(__name__)10app.secret_key = secrets.token_hex(32) # Set SECRET_KEY in Secrets for production1112CLIENT_ID = os.environ['ETSY_CLIENT_ID']13REDIRECT_URI = os.environ['ETSY_REDIRECT_URI']14SCOPES = os.environ.get('ETSY_SCOPES', 'listings_r transactions_r shops_r')1516def generate_pkce():17 """Generate PKCE code verifier and S256 challenge."""18 verifier = secrets.token_urlsafe(43)19 challenge = base64.urlsafe_b64encode(20 hashlib.sha256(verifier.encode()).digest()21 ).rstrip(b'=').decode()22 return verifier, challenge2324@app.route('/auth/login')25def login():26 verifier, challenge = generate_pkce()27 state = secrets.token_hex(16)28 session['pkce_verifier'] = verifier29 session['oauth_state'] = state30 31 params = {32 'response_type': 'code',33 'redirect_uri': REDIRECT_URI,34 'scope': SCOPES,35 'client_id': CLIENT_ID,36 'state': state,37 'code_challenge': challenge,38 'code_challenge_method': 'S256'39 }40 auth_url = 'https://www.etsy.com/oauth/connect?' + '&'.join(f'{k}={v}' for k, v in params.items())41 return redirect(auth_url)4243@app.route('/auth/callback')44def callback():45 code = request.args.get('code')46 state = request.args.get('state')47 48 if state != session.get('oauth_state'):49 return 'Invalid state', 40050 51 verifier = session.pop('pkce_verifier', None)52 if not verifier:53 return 'Missing PKCE verifier', 40054 55 response = requests.post('https://api.etsy.com/v3/public/oauth/token', json={56 'grant_type': 'authorization_code',57 'client_id': CLIENT_ID,58 'redirect_uri': REDIRECT_URI,59 'code': code,60 'code_verifier': verifier61 })62 63 if not response.ok:64 return jsonify({'error': response.json()}), response.status_code65 66 tokens = response.json()67 # Store tokens securely — update Secrets or save to database68 print('Access token received, expires in:', tokens['expires_in'], 'seconds')69 return jsonify({'success': True, 'expires_in': tokens['expires_in']})7071@app.route('/api/receipts')72def get_receipts():73 """Get shop orders (requires OAuth token with transactions_r scope)."""74 token = os.environ.get('ETSY_ACCESS_TOKEN')75 if not token:76 return jsonify({'error': 'ETSY_ACCESS_TOKEN not set — run OAuth flow first'}), 40177 78 # First get shop ID79 me_resp = requests.get(80 'https://openapi.etsy.com/v3/application/users/me/shops',81 headers={'Authorization': f'Bearer {token}', 'x-api-key': CLIENT_ID}82 )83 if not me_resp.ok:84 return jsonify({'error': 'Failed to get shop info'}), me_resp.status_code85 86 shop_id = me_resp.json()['shop_id']87 receipts_resp = requests.get(88 f'https://openapi.etsy.com/v3/application/shops/{shop_id}/receipts',89 headers={'Authorization': f'Bearer {token}', 'x-api-key': CLIENT_ID},90 params={'limit': 25, 'was_paid': True}91 )92 return jsonify(receipts_resp.json())9394if __name__ == '__main__':95 app.run(host='0.0.0.0', port=3000)Pro tip: The base64url encoding for the PKCE challenge must strip padding characters ('='). Python's base64.urlsafe_b64encode() adds padding by default — call .rstrip(b'=') on the result before using it as the code_challenge parameter.
Expected result: Visiting /auth/login redirects to Etsy's OAuth page. After authorization, /auth/callback exchanges the code for tokens. GET /api/receipts returns the shop's paid orders.
Common use cases
Seller Order Management Dashboard
Build a custom order management tool that pulls Etsy receipts (orders) into a unified dashboard alongside orders from other platforms. Track fulfillment status, customer information, and shipping details. Post shipping tracking numbers back to Etsy when orders ship.
Build an Express app with Etsy OAuth PKCE login, a dashboard showing open orders with buyer details, and an endpoint to mark orders as shipped with a tracking number.
Copy this prompt to try it in Replit
Listing Inventory Sync
Sync Etsy listing quantities with an external inventory system. When stock is updated in your warehouse or spreadsheet, automatically update the corresponding Etsy listing quantity via the API. Prevent overselling on Etsy when items are sold on other platforms.
Create an endpoint that accepts a listing ID and new quantity, updates the listing inventory quantity on Etsy via the API, and returns the updated listing details.
Copy this prompt to try it in Replit
Shop Analytics Aggregator
Pull Etsy shop stats — views, favorites, sales counts — and combine them with revenue data from order receipts to build custom analytics reports. Generate weekly summaries or trend charts that Etsy's native analytics does not provide.
Build a Python script that fetches all Etsy receipts from the past 30 days, calculates total revenue by listing category, and outputs a summary report with top-selling items.
Copy this prompt to try it in Replit
Troubleshooting
invalid_grant error during token exchange in the OAuth callback
Cause: The PKCE code verifier was lost between the login and callback requests (e.g., server restarted between steps, in-memory store cleared), or the authorization code has expired (codes expire after a few minutes), or the redirect_uri in the token request does not exactly match the one used in the authorization request.
Solution: Ensure the code verifier is persisted across the redirect (use a database-backed session rather than in-memory storage). Verify that REDIRECT_URI is identical in both the login redirect and the token exchange call — even a trailing slash difference will cause this error. Complete the OAuth flow quickly after starting it.
401 Unauthorized after OAuth completes — API calls fail
Cause: The access token has expired (Etsy tokens expire after 1 hour), the token was stored incorrectly in Secrets with extra whitespace, or the token was issued with insufficient scopes for the endpoint you are calling.
Solution: Check when the token was issued and whether 3600 seconds have passed. Implement a refresh flow using the refresh_token. Also verify the token's scopes match what the endpoint requires — for example, reading orders requires transactions_r scope.
1// Refresh access token2async function refreshToken(refreshToken) {3 const response = await axios.post('https://api.etsy.com/v3/public/oauth/token', {4 grant_type: 'refresh_token',5 client_id: CLIENT_ID,6 refresh_token: refreshToken7 });8 return response.data.access_token;9}redirect_uri_mismatch error when starting OAuth flow
Cause: The ETSY_REDIRECT_URI in Replit Secrets does not exactly match the redirect URI registered in the Etsy developer portal. This includes differences in http vs https, trailing slashes, or path differences.
Solution: Log into the Etsy developer portal and copy the exact registered redirect URI. Update ETSY_REDIRECT_URI in Replit Secrets to match character-for-character. For Replit Autoscale deployments, the URL format is https://{repl-name}.{username}.repl.co/auth/callback.
1// Debug redirect URI mismatch2console.log('Redirect URI:', JSON.stringify(process.env.ETSY_REDIRECT_URI));scope error — endpoint requires scope that was not requested
Cause: The OAuth flow was completed without requesting the necessary scope. For example, reading order receipts requires transactions_r but only listings_r was requested.
Solution: Update ETSY_SCOPES in Replit Secrets to include all required scopes (space-separated), then re-run the OAuth flow. The user must re-authorize with the new scopes. Etsy scope list: listings_r, listings_w, listings_d, transactions_r, transactions_w, billing_r, profile_r, profile_w, email_r, shops_rw, favorites_rw, feedback_r, recommended_listings_r.
Best practices
- Store ETSY_CLIENT_ID, ETSY_REDIRECT_URI, ETSY_SCOPES, and obtained tokens in Replit Secrets (lock icon 🔒) — never hardcode them
- Use server-side session storage (database or Redis) to persist PKCE verifiers across the OAuth redirect flow — in-memory storage is lost on server restart
- Request only the minimum Etsy scopes your app needs — this makes the OAuth consent screen less alarming to users and limits API exposure
- Implement automatic token refresh logic — Etsy access tokens expire after 1 hour; catch 401 responses and use the refresh_token to get a new access_token
- Validate the OAuth state parameter in your callback to prevent CSRF attacks — reject callbacks where the state does not match what you generated
- For public listing search that does not need seller data, use API key auth (x-api-key header) only — save OAuth flow for features that truly need seller account access
- Apply for production API access on the Etsy developer portal early — rate limits and feature availability differ between development and production apps
- Deploy with Autoscale on Replit for OAuth callback handlers and on-demand API endpoints; use Reserved VM only for continuous background sync jobs
Alternatives
eBay has a much larger inventory API covering both auctions and fixed-price listings at a broader scale, but Etsy specializes in handmade and vintage goods with a dedicated seller community.
WooCommerce gives you full control over your own storefront with a simple REST API, but lacks the marketplace discovery that Etsy provides to handmade sellers.
Printful integrates directly with Etsy for print-on-demand fulfillment, making it complementary rather than an alternative — use Printful's API alongside Etsy's for automated POD workflows.
Frequently asked questions
How do I connect Replit to Etsy API?
Register an app at developers.etsy.com to get your API Keystring, implement OAuth 2.0 PKCE in your Replit server to obtain seller access tokens, store the client ID and tokens in Replit Secrets (lock icon 🔒), and call the Etsy Open API v3 endpoints at openapi.etsy.com/v3/application/.
Does Etsy API require a client secret?
No. Etsy Open API v3 uses OAuth 2.0 PKCE (Proof Key for Code Exchange), which does not use a client secret. Instead, your app generates a cryptographic code verifier per-request. You only need your app's Keystring (client ID), the OAuth code, and the code verifier to exchange for tokens — no stored client secret is involved.
How do I store my Etsy API key in Replit?
Click the lock icon (🔒) in the Replit sidebar and add ETSY_CLIENT_ID (your app's Keystring), ETSY_REDIRECT_URI (your OAuth callback URL), and ETSY_SCOPES. After completing the OAuth flow, also store ETSY_ACCESS_TOKEN and ETSY_REFRESH_TOKEN in Secrets. Access them in code with process.env.ETSY_CLIENT_ID (Node.js) or os.environ['ETSY_CLIENT_ID'] (Python).
Can I search Etsy listings without OAuth?
Yes. Public listing data — active listings, shop info, listing photos — can be accessed with just your API Keystring in the x-api-key header. OAuth is only required for seller-specific operations like reading orders, updating listings, or accessing financial data. For read-only marketplace search features, OAuth is not needed.
Why do Etsy access tokens expire so quickly?
Etsy access tokens expire after 3600 seconds (1 hour) for security. Etsy provides refresh tokens that allow you to get a new access token without requiring the user to re-authorize. Implement a refresh flow that catches 401 responses and calls the token endpoint with grant_type=refresh_token. Store the latest access token in your database or update Replit Secrets.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation