# Etsy API

- Tool: Bubble
- Difficulty: Advanced
- Time required: 4–6 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Etsy Open API v3 by configuring the API Connector with TWO mandatory headers on every call — x-api-key (your keystring, marked Private) and Authorization: Bearer {access_token}. Missing either header returns a cryptic 401. Build the PKCE OAuth flow manually via Backend Workflows since Bubble's native OAuth connector does not support PKCE. Set up a Recurring Backend Workflow to refresh the 1-hour access token automatically.

## The Dual-Header API That Trips Every First-Time Builder

Etsy's Open API v3 has a security requirement that is genuinely unusual among e-commerce APIs: every single request must include both an x-api-key header (your static application keystring, always the same regardless of which user is logged in) AND an Authorization: Bearer header (a user-specific access token obtained through OAuth). Most OAuth APIs require only the Bearer token. Etsy requires both simultaneously, and when either is missing, the 401 response gives no indication of which header caused the failure. In Bubble, you solve this by configuring the API Connector group with both headers — x-api-key marked Private so it stays server-side, and Authorization as a shared header where the token is a dynamic parameter your app passes per-call using the token stored for the current logged-in user. The second significant complexity is the OAuth flow itself. Etsy v3 uses PKCE (Proof Key for Code Exchange), which is a security enhancement over standard OAuth that adds a code_verifier/code_challenge pair to the authorization flow. Bubble's built-in OAuth connector does not support PKCE, so the entire flow — generating the state and code_verifier, constructing the authorization URL, handling the redirect callback, exchanging the authorization code for tokens — must be built manually using Bubble Backend Workflows. Backend Workflows (API Workflows) are a paid Bubble plan feature, so this integration requires a paid subscription. Access tokens expire after one hour, meaning a Recurring Backend Workflow that proactively refreshes tokens every 50 minutes is essential for any production app. Once this auth infrastructure is in place, the Etsy API is straightforward to work with: endpoints for listing management, order/receipt retrieval, and shop analytics are well-documented at developers.etsy.com.

## Before you start

- An Etsy seller account with at least one active shop — required to access shop-specific API endpoints like listings and receipts
- An Etsy developer app registered at developers.etsy.com — this generates your keystring (used as both the OAuth client_id and the x-api-key header value)
- A Bubble app on a paid plan — Backend Workflows (API Workflows) are required for the PKCE OAuth token exchange and the Recurring token refresh; these features are not available on Bubble's Free plan
- The API Connector plugin installed in your Bubble app — free, published by Bubble
- Familiarity with Bubble workflows and data types — this integration is rated Advanced and involves multi-step OAuth flows and token storage patterns

## Step-by-step guide

### 1. Register Your Etsy Developer App and Note Your Credentials

Go to developers.etsy.com and sign in with your Etsy account. Navigate to 'Your apps' and click 'Create a new app'. Give your app a name and description. Under the Callback URLs section, add the URL where Etsy will redirect users after authorization — in Bubble, this is your app's URL followed by a page route, for example: https://yourapp.bubbleapps.io/etsy-callback. After creating the app, Etsy displays your keystring on the app detail page. Copy the keystring immediately — this value serves double duty: it is your OAuth client_id (used in the authorization URL) and your x-api-key header value (sent on every API request). Note which OAuth scopes you need: for a seller dashboard with listings and orders, you need at minimum listings_r (read listings), transactions_r (read transactions), and shops_r (read shop info). Etsy does not allow requesting scopes you did not declare at app creation — declare all scopes you may need upfront. If your Etsy app will be used commercially by other sellers (not just your own shop), you must apply for elevated API access through Etsy's developer program — the default registration allows access for testing and personal use.

```
// Etsy OAuth scopes needed for a seller dashboard
// Add these as space-separated values in the authorization URL scope parameter
// listings_r  — read listing data
// transactions_r — read transaction / receipt data
// shops_r    — read shop information
// listings_w  — write (create/update/delete) listings (if needed)
// transactions_w — update transaction data (mark as shipped, etc.)

// Declare all needed scopes when creating your Etsy app — you cannot add scopes later
// without going through Etsy's app review process
```

**Expected result:** You have an Etsy developer app with a keystring (your x-api-key value and OAuth client_id), a configured callback URL pointing to your Bubble app, and the required OAuth scopes declared.

### 2. Set Up the API Connector with Both Required Headers

Open your Bubble app editor and go to Plugins tab → Add plugins. Search for 'API Connector' (published by Bubble) and install it. Open the API Connector configuration and click 'Add another API'. Name the group 'Etsy API' and set the root URL to https://openapi.etsy.com/v3. Now add the two headers that Etsy requires on every call. First header: name it x-api-key, value is your Etsy keystring, and check the 'Private' checkbox — this marks it as a server-side header that Bubble will never send to the browser. Second header: name it Authorization, value is 'Bearer [access_token]' where access_token is a parameter you define (use Bubble's parameter bracket notation). Set access_token as a parameter at the API group level so it can be passed from any call that uses this group. This means the access_token will be dynamically injected per-call from your Bubble database — each user's stored token gets passed as this parameter when you trigger an API call. The authorization header must read exactly: Bearer followed by a space and then the token value. Many 401 errors come from missing the space between 'Bearer' and the token, or from sending the token without the 'Bearer ' prefix.

```
// Etsy API Connector Group Configuration
{
  "groupName": "Etsy API",
  "rootURL": "https://openapi.etsy.com/v3",
  "sharedHeaders": [
    {
      "name": "x-api-key",
      "value": "YOUR_ETSY_KEYSTRING",
      "private": true
    },
    {
      "name": "Authorization",
      "value": "Bearer <access_token dynamic>",
      "private": false
    },
    {
      "name": "Accept",
      "value": "application/json",
      "private": false
    }
  ],
  "groupParameters": [
    {
      "name": "access_token",
      "description": "Per-user Etsy access token from Bubble DB"
    }
  ]
}
```

**Expected result:** The Etsy API group appears in the API Connector with the x-api-key Private header and the Authorization Bearer header configured. No calls have been added yet — you add those in subsequent steps after setting up the OAuth flow.

### 3. Build the PKCE OAuth Authorization Flow Using Backend Workflows

Etsy v3 requires PKCE OAuth, and Bubble's built-in OAuth connector does not support PKCE. You must build the entire authorization flow manually. PKCE adds a code_verifier (a random string) and code_challenge (SHA-256 hash of the code_verifier, base64url encoded) to the standard OAuth code flow. Since Bubble cannot generate SHA-256 hashes natively, use the Toolbox plugin's 'Run javascript' action in a Backend Workflow to generate the PKCE values. Install the Toolbox plugin from Plugins → Add plugins → search 'Toolbox'. Step 1 — Generate state and PKCE: Create a Backend Workflow named 'Etsy_GeneratePKCE'. Inside it, add a 'Run javascript' action (from Toolbox) that generates a random 43-128 character code_verifier string. Store the code_verifier in a Bubble data type named 'EtsyOAuthSession' with fields: code_verifier (text), state (text), user (User), created_date (date). Step 2 — Build authorization URL: Construct the Etsy authorization URL as a text value in a workflow. The URL format is: https://www.etsy.com/oauth/connect?response_type=code&redirect_uri={YOUR_CALLBACK_URL}&scope={SCOPES}&client_id={KEYSTRING}&state={state}&code_challenge={code_challenge}&code_challenge_method=S256. Step 3 — Redirect user: Use Bubble's 'Go to URL' action to send the user to this authorization URL. They will see Etsy's permission screen and authorize your app. Step 4 — Handle callback: On your Etsy callback page (/etsy-callback), the URL will contain 'code' and 'state' query parameters. Extract them from the page URL. Verify the state matches the stored EtsyOAuthSession. Trigger a Backend Workflow that exchanges the code for tokens.

```
// JavaScript for Toolbox 'Run javascript' action — generates PKCE code_verifier and code_challenge
// Add this in the Toolbox Run javascript action body
// Output properties: code_verifier (text), code_challenge (text), state (text)

function generateRandomString(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
  let result = '';
  const array = new Uint8Array(length);
  window.crypto.getRandomValues(array);
  array.forEach(byte => result += chars[byte % chars.length]);
  return result;
}

async function generatePKCE() {
  const code_verifier = generateRandomString(64);
  const state = generateRandomString(32);
  const encoder = new TextEncoder();
  const data = encoder.encode(code_verifier);
  const digest = await window.crypto.subtle.digest('SHA-256', data);
  const code_challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  bubble_fn_result({ code_verifier, code_challenge, state });
}

generatePKCE();
```

**Expected result:** Users who click 'Connect Etsy' are redirected to Etsy's authorization page. After authorizing, they are redirected back to your Bubble callback page with a 'code' query parameter in the URL. The EtsyOAuthSession record in your database contains the matching code_verifier and state values.

### 4. Exchange Authorization Code for Tokens and Store Them Securely

After the user authorizes your Etsy app, Etsy redirects them to your callback URL with a 'code' parameter and the 'state' value. Create a Backend Workflow named 'Etsy_ExchangeCode' that takes the authorization code as an input parameter. This workflow makes a POST call to Etsy's token endpoint: https://api.etsy.com/v3/public/oauth/token. The POST body must include: grant_type=authorization_code, client_id={KEYSTRING}, redirect_uri={YOUR_CALLBACK_URL}, code={authorization_code}, and code_verifier={the stored code_verifier from EtsyOAuthSession}. Set this up as a separate API call in your API Connector — name the group 'Etsy OAuth' with root URL https://api.etsy.com (note: different from the API root https://openapi.etsy.com). The token exchange call does NOT need the x-api-key header — only the form-encoded body parameters. Set the call body as application/x-www-form-urlencoded (not JSON). Etsy returns: access_token (use this for all API calls), refresh_token (save this for renewal), expires_in (seconds — typically 3600). Create a Bubble data type 'EtsyToken' with fields: access_token (text), refresh_token (text), expires_at (date), shop_id (number), user (User). Store all token fields immediately after the exchange. Delete the EtsyOAuthSession record once the exchange succeeds to remove the short-lived code_verifier from your database.

```
// Etsy token exchange POST body (application/x-www-form-urlencoded)
grant_type=authorization_code
&client_id=YOUR_ETSY_KEYSTRING
&redirect_uri=https://yourapp.bubbleapps.io/etsy-callback
&code=AUTHORIZATION_CODE_FROM_REDIRECT
&code_verifier=THE_STORED_CODE_VERIFIER

// Etsy token exchange response
{
  "access_token": "etsy-v3-access-token-value",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "etsy-v3-refresh-token-value"
}

// Store in Bubble EtsyToken data type:
// access_token: response.access_token
// refresh_token: response.refresh_token
// expires_at: Current date/time + 3600 seconds (add 3600 seconds to now)
// user: Current User
```

**Expected result:** After authorization, an EtsyToken record is created in Bubble's database for the user with access_token, refresh_token, and expires_at fields populated. The user can now make authenticated Etsy API calls.

### 5. Add and Initialize Etsy API Calls and Build the Seller Dashboard

With authentication working, add API calls to your Etsy API group. Start with the user profile call to confirm both headers work: GET /application/users/me. When initializing this call, use a real access_token value from the EtsyToken record you created in Step 4 — paste it as the access_token parameter during initialization. If initialization succeeds, Bubble detects the user fields. Next, add the active listings call: GET /application/shops/{shopId}/listings/active. The shopId is available from the GET /users/me response (shops array, first item's shop_id field — store it in EtsyToken when you run the initial user lookup). Add parameters: limit (default 25, max 100), offset (for pagination). Initialize with real values. Etsy list responses wrap data in {count, results: [...]} — use results as the data path in Bubble's API Call definition. Add one more call: GET /application/shops/{shopId}/receipts for order management. Use parameters: min_created, max_created (Unix timestamps), status. Now build the seller dashboard page: add a Repeating Group bound to the listings call, passing the current user's shopId and access_token from EtsyToken. Display listing title, price, quantity, and thumbnail image. Add a second section for recent orders bound to the receipts call. Important: Etsy's daily limit is 10,000 requests per app across all your users. A Repeating Group that auto-refreshes on every page load can exhaust this quickly for a popular app — cache listing data in a Bubble data type and refresh it on a schedule or on-demand rather than fetching live for every visitor.

```
// Etsy GET /application/users/me response structure (partial)
{
  "user_id": 12345678,
  "primary_email": "seller@example.com",
  "shops": [
    {
      "shop_id": 87654321,
      "shop_name": "MyEtsyShop"
    }
  ]
}

// Etsy GET /application/shops/{shopId}/listings/active response structure
{
  "count": 48,
  "results": [
    {
      "listing_id": 1234567890,
      "title": "Handmade Ceramic Mug - Ocean Blue",
      "price": { "amount": 2500, "divisor": 100, "currency_code": "USD" },
      "quantity": 5,
      "state": "active",
      "url": "https://www.etsy.com/listing/1234567890/handmade-ceramic-mug",
      "images": [
        { "listing_image_id": 1111, "url_170x135": "https://...", "url_570xN": "https://..." }
      ]
    }
  ]
}
```

**Expected result:** The Etsy API Connector has initialized calls for user profile and shop listings. The seller dashboard page displays the authenticated seller's active listings with titles, prices, and images. Recent orders appear in the orders section.

### 6. Set Up Recurring Token Refresh to Prevent Expiry

Etsy access tokens expire after exactly one hour. Any API call made with an expired token returns a 401 error, breaking your app for logged-in users. The solution is a Recurring Backend Workflow that proactively refreshes the token before it expires. Add a new API call to your 'Etsy OAuth' group: POST to https://api.etsy.com/v3/public/oauth/token with grant_type=refresh_token, client_id={KEYSTRING}, refresh_token={stored_refresh_token}. Etsy returns a new access_token and refresh_token pair. Create a Backend Workflow named 'Etsy_RefreshToken' that takes a user as input. It calls the refresh token endpoint with that user's stored refresh_token, then updates the EtsyToken record: set access_token to the new value, set refresh_token to the new value (Etsy rotates refresh tokens), and set expires_at to current time + 3600 seconds. Create a Recurring Backend Workflow (Settings → Backend Workflows → Recurring events in Bubble) that runs every 50 minutes. This workflow searches for all EtsyToken records where expires_at is within the next 10 minutes and calls 'Etsy_RefreshToken' for each one. Alternatively, you can add a token freshness check at the beginning of any workflow that makes an Etsy API call: if the current user's EtsyToken expires_at is within 5 minutes of now, run the refresh before making the API call. Both approaches are valid; the Recurring Backend Workflow is more reliable for apps with many concurrent users. Note: Recurring Backend Workflows require a paid Bubble plan and consume Workload Units — budget for this when planning your Bubble subscription tier. If you have complex questions about building this auth infrastructure for a production Etsy integration, RapidDev's team can help plan the architecture — free scoping call at rapidevelopers.com/contact.

```
// Etsy token refresh POST body (application/x-www-form-urlencoded)
grant_type=refresh_token
&client_id=YOUR_ETSY_KEYSTRING
&refresh_token=THE_STORED_REFRESH_TOKEN

// Etsy refresh token response
{
  "access_token": "new-etsy-access-token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "new-etsy-refresh-token"
}

// IMPORTANT: Etsy rotates refresh tokens on each use.
// Always save the NEW refresh_token from the response — the old one is invalidated.
// Update EtsyToken record:
// access_token = response.access_token
// refresh_token = response.refresh_token  (NEW value)
// expires_at = now + 3600 seconds
```

**Expected result:** The Recurring Backend Workflow runs every 50 minutes and refreshes EtsyToken records for users whose tokens are expiring soon. Users stay logged in indefinitely without seeing 401 errors from expired tokens.

## Best practices

- Always configure BOTH the x-api-key and Authorization: Bearer headers in your API Connector group — missing either one causes a 401 with no indication of which header is the problem.
- Store Etsy access_token and refresh_token in a dedicated Bubble data type (e.g., EtsyToken) with privacy rules that restrict access to the token's owner — never expose tokens in page URL parameters or client-visible state.
- Set up a Recurring Backend Workflow to refresh access tokens every 50 minutes — do not wait for a 401 error to trigger the refresh, as this breaks the current user's session mid-interaction.
- Always save the NEW refresh_token returned from each refresh call — Etsy rotates refresh tokens, and failing to update the stored value will break the next refresh.
- Cache Etsy listing and order data in Bubble's database for frequently viewed content. The 10,000 requests per day per app limit is easy to exhaust with a popular app that fetches live data for every page load.
- Request only the OAuth scopes your app actually needs when creating your Etsy developer app. Additional scopes can only be added through Etsy's app review process — plan ahead rather than starting with minimal scopes.
- Apply Bubble privacy rules to your EtsyToken data type immediately after creating it. By default all authenticated Bubble users can read all records of a given type — restrict EtsyToken access to the Current User whose record it is.
- Test the token expiry and refresh flow in development before launching — manually expire a token (by editing the expires_at field to a past date) and confirm the refresh workflow picks it up and the user can continue making API calls without re-authorizing.

## Use cases

### Etsy Seller Dashboard

Build a custom admin panel in Bubble for Etsy shop owners to view their active listings, pending orders, and recent transactions in a consolidated interface. Pull active listings from the Etsy listings API and receipts from the transactions API to give sellers a single-screen view without logging into Etsy's native dashboard.

Prompt example:

```
Show all active listings in my Etsy shop sorted by number of sales descending, with the listing image, title, price, and current quantity.
```

### Multi-Shop Analytics Tool

Allow multiple Etsy sellers to connect their shops to your Bubble app using the OAuth flow. Each seller authorizes their own account, stores their own access token in your Bubble database, and can view shop-specific analytics — revenue trends, top-selling listings, order fulfillment rates — pulled live from the Etsy API.

Prompt example:

```
Show me the total revenue and number of orders for my Etsy shop for each of the last 6 months, broken down by listing category.
```

### Automated Order Fulfillment Tracker

Build a Bubble workflow that checks new Etsy receipts on a schedule, creates fulfillment records in Bubble's database, and sends internal notifications (via Slack or email) when new orders arrive. Update order fulfillment status back to Etsy via the receipts API when items ship.

Prompt example:

```
Show all unshipped Etsy orders from the last 30 days with the buyer's name, shipping address, and ordered items listed.
```

## Troubleshooting

### API calls return HTTP 401 even though the Bearer token looks correct

Cause: The x-api-key header is missing, incorrectly spelled, or has the wrong value. Etsy v3 requires BOTH x-api-key and Authorization: Bearer simultaneously — a missing x-api-key returns the same 401 as a bad Bearer token, with no distinction in the error message.

Solution: Open the API Connector group configuration and verify that x-api-key is spelled exactly lowercase (not X-Api-Key, not X-API-KEY), that the value is your Etsy keystring (not the OAuth client secret — there is only one credential: the keystring), and that the Private checkbox is checked. Re-initialize a simple call like GET /application/users/me with your stored access_token to confirm both headers are working together.

### 'There was an issue setting up your call' during Initialize — no fields detected

Cause: The access_token value you used during initialization was expired (tokens last 1 hour) or the token does not have the required OAuth scope for the endpoint you are initializing.

Solution: Complete the OAuth flow fresh (Steps 3 and 4) to get a new access_token, then use that value during initialization. For the /listings endpoint, ensure your Etsy app was created with the listings_r scope. Initialize with /application/users/me first (requires no shop-specific scope) to confirm the headers are valid, then initialize the more specific endpoints.

### Token exchange fails with 'invalid_grant' or 'invalid_client'

Cause: The authorization code from Etsy has already been used (codes are single-use), the code_verifier does not match the code_challenge submitted in the authorization URL, or the redirect_uri in the token exchange does not exactly match the one registered in your Etsy developer app.

Solution: Restart the OAuth flow completely: generate a new code_verifier and state, redirect the user to Etsy's authorization page again, and use the new authorization code immediately on the callback. Verify that the redirect_uri in the token exchange POST body is character-for-character identical to the one listed in your Etsy app settings — trailing slashes and http vs https differences matter.

### App stops working with 401 errors after about 1 hour of use

Cause: The Etsy access token expired after 1 hour and the refresh flow is not running, not finding the right record, or using an already-rotated refresh_token.

Solution: Check your Recurring Backend Workflow in Bubble's Workflow Logs. If it is not running, verify it is scheduled and that your Bubble plan supports recurring events. If it is running but failing, check the EtsyToken record — Etsy rotates refresh_tokens on each use, so if a previous refresh ran but did not save the new refresh_token, the stored token is now invalid. Run a fresh OAuth flow for the affected user to get a new token pair.

### API returns 429 Too Many Requests during peak usage

Cause: Etsy allows 10 requests per second and 10,000 requests per day per application. A Bubble Repeating Group that auto-fetches listing data on every page load for multiple users can exhaust the daily limit quickly.

Solution: Cache Etsy listing data in a Bubble data type (sync on demand or on a schedule, not live per page load). For the daily request count, implement a simple counter in a Bubble database field that increments per API call — alert your team when nearing the limit. For per-second rate limiting, add a Wait action (0.5-1 second) between API calls in loops within Backend Workflows.

## Frequently asked questions

### Do I need a paid Bubble plan to connect to the Etsy API?

Yes, for a complete and production-ready integration. The PKCE OAuth token exchange and the Recurring token refresh both require Backend Workflows (API Workflows), which are only available on paid Bubble plans. If you only need read-only access for your own shop (using a pre-obtained static token), you could set up API calls without Backend Workflows — but the token expires every hour and you would need to manually refresh it, which is not practical for a production app.

### Why does Etsy require two headers (x-api-key and Authorization) when most APIs only need one?

Etsy's dual-header design separates application identity from user identity. The x-api-key identifies your registered developer application to Etsy — it is the same on every call regardless of which user is logged in. The Authorization Bearer token identifies the specific Etsy user (shop owner) who has authorized your app. Together they let Etsy track both per-app usage (for rate limiting and compliance) and per-user permissions (for security and scope enforcement).

### Can I use Bubble's built-in OAuth connector for Etsy?

No. Bubble's native OAuth connector does not support PKCE (Proof Key for Code Exchange), which Etsy v3 requires. The PKCE flow adds a code_verifier and code_challenge to the authorization URL, and the code_verifier must be sent during token exchange. You must build this flow manually using Bubble Backend Workflows and the Toolbox plugin's 'Run javascript' action to generate the PKCE values using the browser's Web Crypto API.

### What happens if I do not refresh the Etsy access token?

After one hour, all Etsy API calls made with the expired token return HTTP 401 errors, breaking any workflow in your Bubble app that depends on the Etsy API. Users would need to re-authorize through the OAuth flow to get a new token. For a production app with active users, proactive token refresh via a Recurring Backend Workflow is mandatory — treat it as a core infrastructure component, not an optional enhancement.

### Can I build an Etsy integration for multiple sellers (a multi-tenant app)?

Yes — the architecture described here supports multiple sellers. Each seller who connects their Etsy shop goes through the OAuth flow, and their tokens are stored as separate EtsyToken records linked to their Bubble User record. Your API calls pass the current user's stored token as the access_token parameter. The Recurring Backend Workflow refreshes all EtsyToken records that are approaching expiry, regardless of which user they belong to. The Etsy 10,000 requests per day limit applies across ALL users of your app combined — plan your caching strategy accordingly.

### How do I get my Etsy shop ID to use in API calls?

After completing the OAuth flow, call GET /application/users/me with the user's access_token. The response includes a 'shops' array — the first item's 'shop_id' field is the numeric shop ID you need for all subsequent shop-specific endpoints like /application/shops/{shopId}/listings/active. Store the shop_id in the EtsyToken data type record alongside the tokens so you do not need to fetch it on every request.

---

Source: https://www.rapidevelopers.com/bubble-integrations/etsy-api
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/etsy-api
