---
name: gumroad-api
description: Manage Gumroad products, sales, offer codes, licenses, subscribers, variants, and webhooks through the REST API v2. Use when checking sales data, creating discount codes, toggling or updating products, verifying license keys, pulling subscriber lists, or wiring sale webhooks. Not for creating new products or any browser-only flow: the API cannot create products, use gumroad-browser for that.
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: original
  category: Sales & RevOps
---

# Gumroad API Management

The Gumroad API v2 handles everything after a product exists: sales pulls, discounts, licenses, subscribers, webhooks. It cannot create products. Product creation goes through gumroad-browser, always.

## Basics

- Base URL: `https://api.gumroad.com/v2/`. All responses are JSON with a `success` boolean.
- Auth: an access token generated under Settings > Advanced in the Gumroad dashboard. Pass it as `?access_token=<GUMROAD_ACCESS_TOKEN>`, a POST body field, or an `Authorization: Bearer` header.
- Writes use `Content-Type: application/x-www-form-urlencoded`, not JSON bodies.
- All money values are integer cents. `price=4900` is $49.00.

Store the token in an env variable. Never commit it or echo it into logs.

## Operation routing

Read references/endpoints.md when you need exact parameters, field lists, webhook payload shape, or MCP server setup. Route by task:

| Task | Method and path |
|---|---|
| List products / get product IDs | `GET /products` |
| Update product fields, price, publish state | `PUT /products/:id` |
| Enable or disable a product | `PUT /products/:id/enable` or `/disable` |
| Pull sales, filter by date | `GET /sales?after=<ISO>&before=<ISO>` |
| Create or manage discount codes | `POST/PUT/DELETE /products/:id/offer_codes` |
| Variants and tiers | `/products/:id/variant_categories/...` |
| Checkout custom fields | `/products/:id/custom_fields` |
| Verify, enable, disable licenses | `POST /licenses/verify`, `PUT /licenses/enable` etc. |
| Subscriber lists | `GET /products/:id/subscribers` |
| Webhooks (resource subscriptions) | `POST /resource_subscriptions` |
| Authenticated account info | `GET /user` |

## Standard workflows

Monthly sales pull:

```bash
curl "https://api.gumroad.com/v2/sales?access_token=$GUMROAD_ACCESS_TOKEN&after=<MONTH_START>&before=<MONTH_END>"
```

Launch discount across products: `GET /products` for IDs, then per product:

```bash
curl -X POST "https://api.gumroad.com/v2/products/<PRODUCT_ID>/offer_codes" \
  -d "access_token=$GUMROAD_ACCESS_TOKEN" \
  -d "name=<CODE>" -d "amount_off=20" -d "offer_type=percent" -d "max_purchase_count=50"
```

Sale webhook to an automation endpoint:

```bash
curl -X POST "https://api.gumroad.com/v2/resource_subscriptions" \
  -d "access_token=$GUMROAD_ACCESS_TOKEN" \
  -d "resource_name=sale" -d "post_url=<WEBHOOK_URL>"
```

## Error handling

- 401: invalid or missing token. 404: resource not found. 422: invalid parameters. 429: rate limited, back off and retry.
- Every error body carries `success: false` and a `message`. Check `success` on 200s too; some failures return 200 with `success: false`.
- Paginate `GET /sales` with the `page` parameter for any range that might exceed one page.

## Good vs bad

Good discount setup: create the code with `max_purchase_count` set, then GET it back and confirm `amount_off`, `offer_type`, and the cap before announcing the code anywhere.

Bad: fire the POST, see HTTP 200, and post the code publicly. `offer_type` defaulted wrong or the body was ignored, and a 20 percent code just became 20 cents off, or the cap is unlimited. The announcement makes the mistake irreversible.

## Verification

Do this: after any write (product update, offer code, webhook, license change), GET the same resource back and compare every field you set. Expect the response to show `success: true` and the exact values sent. If a field differs or the GET 404s, the write did not take: fix the request before reporting the operation done.

## Completion checklist

- [ ] Token sourced from env, never hardcoded or logged
- [ ] Every write verified with a read-back
- [ ] Money values handled as integer cents
- [ ] Date-filtered pulls checked for pagination
- [ ] Anything needing product creation routed to gumroad-browser

Any box unchecked: not done. Fix or say so.

## Footguns

- Trying to create a product via the API: no such endpoint exists. Route to gumroad-browser instead of improvising with PUT.
- `amount_off` is unit-ambiguous: cents when `offer_type=cents`, a percentage when `offer_type=percent`. Always send both fields together and verify with a read-back.
- Sending JSON bodies: the API expects form encoding. JSON payloads fail or silently ignore fields.
- Trusting HTTP 200: check the `success` field in the body. 200 with `success: false` is a failure.
- Webhook endpoints receive buyer emails and order data: point `post_url` only at endpoints you control over HTTPS, and treat the payload as PII.
