Etsy uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) for authentication. This is more secure than simple API keys because:
Here’s the complete flow for obtaining access:
┌──────────────────────────────────────────────────────────┐
│ 1. Generate code_verifier and code_challenge │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 2. Redirect user to Etsy authorization URL │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 3. User logs in and grants permissions │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 4. Etsy redirects to your callback with auth code │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 5. Exchange auth code for access token │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 6. Use access token for API calls │
└──────────────────────────────────────────────────────────┘
PKCE adds security by generating a random code_verifier and its hashed code_challenge:
import secrets
import hashlib
import base64
def generate_pkce_pair():
# Generate random verifier (43-128 characters)
code_verifier = secrets.token_urlsafe(32)
# Create SHA256 hash and base64url encode it
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
return code_verifier, code_challenge
The code_challenge is sent in the authorization request; the code_verifier is sent when exchanging the code for a token. This prevents interception attacks.
Construct the URL where users grant permissions:
from urllib.parse import urlencode
from config import Config
def get_authorization_url(code_challenge, state):
params = {
"response_type": "code",
"client_id": Config.API_KEY,
"redirect_uri": Config.REDIRECT_URI,
"scope": "listings_r listings_w transactions_r shops_r",
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256"
}
return f"https://www.etsy.com/oauth/connect?{urlencode(params)}"
The state parameter is a random string to prevent CSRF attacks.
After the user grants permission, Etsy redirects to your redirect_uri with:
code: The authorization codestate: Your original state (verify it matches!)For local development, you can run a simple server:
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self):
query = parse_qs(urlparse(self.path).query)
auth_code = query.get("code", [None])[0]
# Save auth_code and exchange for token
self.send_response(200)
self.end_headers()
self.wfile.write(b"Authorization complete! Check console.")
Exchange the authorization code for access and refresh tokens:
import requests
def exchange_code_for_token(auth_code, code_verifier):
response = requests.post(
"https://api.etsy.com/v3/public/oauth/token",
data={
"grant_type": "authorization_code",
"client_id": Config.API_KEY,
"redirect_uri": Config.REDIRECT_URI,
"code": auth_code,
"code_verifier": code_verifier
}
)
return response.json()
The response includes:
{
"access_token": "12345678.abcdefg...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "87654321.hijklmn..."
}
Store tokens securely for reuse. See code/auth.py for a complete implementation.
import json
from pathlib import Path
TOKEN_FILE = Path("tokens.json")
def save_tokens(tokens):
TOKEN_FILE.write_text(json.dumps(tokens))
def load_tokens():
if TOKEN_FILE.exists():
return json.loads(TOKEN_FILE.read_text())
return None
Access tokens expire (typically after 1 hour). Use the refresh token to get new ones:
def refresh_access_token(refresh_token):
response = requests.post(
"https://api.etsy.com/v3/public/oauth/token",
data={
"grant_type": "refresh_token",
"client_id": Config.API_KEY,
"refresh_token": refresh_token
}
)
return response.json()
The full implementation in code/auth.py handles:
Here’s how you’ll use it in your scripts:
from auth import get_access_token
# This handles everything: load from file, refresh if needed, or start OAuth
token = get_access_token()
# Use the token in API calls
headers = {"Authorization": f"Bearer {token}"}
For the initial authorization, run:
python auth.py
This will:
You only need to do this once. After that, tokens refresh automatically.
tokens.json to .gitignore.envhttp:// or https://)http://localhost:3000/callback (register it first)code_verifier matches the original code_challengeCreate a simple test script:
# test_auth.py
from auth import get_access_token
import requests
token = get_access_token()
response = requests.get(
"https://openapi.etsy.com/v3/application/openapi-ping",
headers={"x-api-key": Config.API_KEY}
)
print(f"API Status: {response.json()}")
With authentication working, you can now make authenticated API calls. The next chapter covers retrieving and managing your shop information.
| ← Previous: Getting Started | Next: Shop Management → |