SKILL.md
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 asuccessboolean. - 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 anAuthorization: Bearerheader. - Writes use
Content-Type: application/x-www-form-urlencoded, not JSON bodies. - All money values are integer cents.
price=4900is $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:
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:
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:
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: falseand amessage. Checksuccesson 200s too; some failures return 200 withsuccess: false. - Paginate
GET /saleswith thepageparameter 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_offis unit-ambiguous: cents whenoffer_type=cents, a percentage whenoffer_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
successfield in the body. 200 withsuccess: falseis a failure. - Webhook endpoints receive buyer emails and order data: point
post_urlonly at endpoints you control over HTTPS, and treat the payload as PII.