gumroad CLI
Use gumroad (Gumroad CLI) to query and manage Gumroad data.
Agent invariants
Always follow these rules:
- Always pass
--no-input to prevent interactive prompts from blocking.
- Always pass
--json for programmatic access.
- Use
--json --jq <expr> together to extract exactly what you need.
- For operations that can prompt for confirmation (delete, refund, workflow step adds, workflow delay changes, mutating admin actions,
files abort, files complete replay, product updates that remove files, or products content set when omitted page IDs will be deleted), add --yes to skip confirmation.
- Pass
--quiet to suppress spinners and status messages.
- Pass
--dry-run to preview mutating requests without executing them.
- Use
--page-delay 200ms with --all to avoid rate limits on large datasets.
- Prices are in whole currency units (e.g.
--price 10.00 for $10), not cents. The CLI converts internally. Use --currency eur to change currency.
- Products are created as drafts — use
gumroad products publish <id> to make them live.
- Product cover and thumbnail uploads support JPEG, PNG, and GIF. WebP is not supported by the API and the CLI rejects it before upload.
- Product custom HTML landing pages use
gumroad products page preview <id> ./landing.html to run the backend sanitizer without writing, gumroad products page publish <id> ./landing.html to store the page, gumroad products page clear <id> --yes to remove it, and gumroad products page url <id> to print the live URL. --dry-run only previews the CLI request body; it does not call the backend sanitizer. Inspect .sanitization_report in preview and publish JSON output for server-side changes.
- Profile custom HTML landing pages mirror the product commands without a product id and without checkout:
gumroad user page preview ./landing.html, gumroad user page publish ./landing.html (read from stdin with -), gumroad user page clear --yes, and gumroad user page url (prints the public profile URL and its /landing/embed URL). A profile has no buy button, so omit data-gumroad-action="buy" and the checkout data attributes; link to products instead.
- Storefront pages (slugged pages serving at
<username>.gumroad.com/<slug>) use gumroad pages list, gumroad pages create --title <title> [--slug <slug>] [path] (with an HTML path or - the page is created as custom HTML; without one it starts empty for the in-app editor), gumroad pages pull <slug> to download a page's existing custom HTML so pull → edit → push is a real round trip (writes <slug>.html; -o <path> to choose, -o - for stdout; refuses to overwrite without --force; errors with a hint when the page has no custom HTML), gumroad pages scaffold <slug> to generate starter HTML for a rich-text page or a default profile from a static snapshot of its current render (same flags as pull; pushing the scaffold converts the page to custom HTML and replaces the dynamic storefront/editor experience), gumroad pages push <slug> ./page.html to replace a page with custom HTML, and gumroad pages preview ./page.html to run the backend sanitizer without publishing. The loop for going custom: pull (or scaffold when there's no custom HTML yet) → edit → preview → push. --json/--jq on pull/scaffold still write the file and additionally print the raw API response; combining them with -o - is rejected since both would own stdout. The special slug profile targets the profile landing page (pull profile downloads your published custom HTML, scaffold profile snapshots the default storefront render when none is published; push uses the same endpoints as user page publish). Custom HTML pages require the seller's custom_html_pages feature to be enabled; without it writes fail with an access message. Writes to slugged pages (pages create/pages push <slug>) also require the token to carry the edit_profile scope — tokens minted before the CLI requested that scope get a 403 telling them to re-run gumroad auth login.
- Product rich content uses
gumroad products content list <id> --json --no-input to inspect page IDs, gumroad products content get <id> --json --no-input to dump the shared rich_content page array, and gumroad products content set <id> content.json --dry-run --json --no-input to preview a whole-document replacement. Without an explicit path, whole-document set reads ./content.json; set --page reads ./page.json. Use --page <page_id> with get/set to edit one matching page object; set --page still sends a merged whole-document PUT. For per-variant content, pass both --variant <variant_id> and --category <cat_id>. Whole-document set deletes existing pages omitted from the JSON.
- Custom HTML pages can use
data-gumroad-field="name", data-gumroad-field="price", data-gumroad-field="description", and data-gumroad-action="buy". To preselect checkout state, add data-gumroad-option="<variant name>", data-gumroad-quantity="<integer>", data-gumroad-price="<decimal>", or data-gumroad-recurrence="monthly|quarterly|biannually|yearly|every_two_years". Production validates these values and falls back to product defaults when invalid. Prefer anchors for buy CTAs so production can add a checkout href; non-anchor buy elements also post to checkout.
- Audience emails are created as drafts by default. Use
gumroad emails send-preview <id> --json --no-input and inspect .preview_url before gumroad emails send <id> --yes --json --no-input. Creating with --send publishes and blasts immediately, so use --dry-run first and require explicit human approval. To schedule a send instead of blasting immediately, create the draft then gumroad emails schedule <id> --at "<RFC3339>" --json --no-input; gumroad emails unschedule <id> returns a scheduled email to draft.
- Workflow email writes do not change the workflow publication state. Adding a step to a published workflow can schedule eligible past recipients. Changing a delay can reschedule recipients. Use
--dry-run before each write.
- If a command fails with a seller auth error, run
gumroad auth status --json --no-input first. Agents can start seller auth with gumroad auth login --no-input and hand the printed approval URL to a human, or use an existing seller token via GUMROAD_ACCESS_TOKEN or gumroad auth login --with-token.
- For admin commands in agents/CI, pass
--non-interactive and set GUMROAD_ADMIN_TOKEN; interactive shells can store an admin token with gumroad auth login --web.
Connect from Claude Desktop / Cursor / Claude Code
- Run
gumroad mcp to serve the public CLI commands as MCP tools over stdio.
- Log in first with
gumroad auth login, or pass GUMROAD_ACCESS_TOKEN in the MCP server environment. The server starts without a token, but calls return a login hint until credentials are available.
Add this to your client's MCP configuration (use the absolute path to gumroad if it is not on the client's PATH):
{"mcpServers":{"gumroad":{"command":"gumroad","args":["mcp"]}}}
Tools follow CLI leaf command paths with underscores, including hyphens converted to underscores: products_list, products_view, offer_codes_list, sales_refund. Input keys are the original long flag names; repeatable flags take arrays of strings, and positional arguments go in args (for example, {"args":["<product-id>"]}). Defaults stay with the CLI, and its validators report missing arguments or flags. Runnable groups such as gumroad user are exposed too (user reads the account); use user or products_list as a connection check.
Every call uses a fresh command with --json --no-input --quiet and automatically passes --yes where available. Mutations run immediately, without interactive confirmation; require approval in the MCP client before sending a mutating call. Use dry-run: true when supported to preview requests. Auth, admin, completion, skill, help, MCP itself, and hidden/deprecated commands are excluded. The server uses only the seller token, never the admin token.
Only connect trusted clients: tools have the same local file access as the CLI, including uploads and downloads. File paths are on the machine running the server. Stdin-based content input is unavailable; provide file paths or explicit flags instead. HTTP transport is not supported. Annotations are conservative: list/view/get/preview/pull/download-style commands are marked read-only; every other tool (including licenses_verify, which increments uses unless no-increment is true, pages_push and emails_send) carries destructiveHint so clients ask before running it. They describe the command category, not a security boundary (downloads still write local files).
Response shapes
Most responses are wrapped in {"success": true, ...} with resource-specific keys:
user → .user, user update → .user
user page preview → .custom_html, .sanitization_report
user page publish / user page clear → .custom_html, .previous_custom_html, .profile_url, .sanitization_report
user page url → .profile_url, .has_landing_page
pages list → .pages[] (.slug, .title, .content, .custom_html, .url); pages create / pages push <slug> → .page; pages pull <slug> / pages scaffold <slug> → .page + .rendered_html (pull writes the page's .page.custom_html; scaffold writes .rendered_html, the static render snapshot); pages pull profile / pages scaffold profile → .custom_html, .rendered_html, .has_landing_page, .profile_url; pages push profile → profile shape (.custom_html, .previous_custom_html, .profile_url, .sanitization_report); pages preview → .custom_html, .sanitization_report
refund-policy view/set → .refund_policy
products list → .products[]
products view → .product
products content get → rich content page array directly, or one page object with --page
products content list → rich content page summary array directly
products content set → mutation envelope with .result
sales list → .sales[]
sales buyers → .buyers[] (email, name, purchase_count, last_purchase_date, utm_source, utm_medium, utm_campaign, utm_term, utm_content)
sales view → .sale (includes .currency, the ISO code the sale is priced in — the same currency a refund amount is read in)
sales export → .status, .recipient_email
sales summary → .gross_cents, .net_cents, .breakdown[]
emails list → .emails[], emails view/create/send/schedule/unschedule → .email, emails send-preview → .preview_url, emails delete → .message
workflows list → .workflows[], workflows view → .workflow, workflows add-email/update-email → .email
payouts list → .payouts[], payouts view/upcoming → .payout
subscribers list → .subscribers[], subscribers view → .subscriber
licenses verify → .purchase
offer-codes list → .offer_codes[]
upsells list → .upsells[], upsells view/create/update → .upsell, upsells delete → .message
variant-categories list → .variant_categories[]
variants list → .variants[]
files upload / files complete → .file_url
media upload → .media (.id, .name, .url, .file_size), media list → .media[], media delete → .message
products create with media flags → .product plus .media[]
products update with media flags → .product plus .media[]
products covers add --image → .result.covers[], .result.main_cover_id, plus .result.media[]
products covers add --url → .result.covers[], .result.main_cover_id
products thumbnail set --image → .result.thumbnail, plus .result.media[]
products thumbnail set --url → .result.thumbnail
products page preview → .custom_html, .sanitization_report
products page publish / products page clear → .product.custom_html, .product.landing_url, .previous_custom_html, .sanitization_report
products update --custom-html → .product.custom_html, .product.landing_url, .previous_custom_html, .sanitization_report
products create/update --refund-period/--refund-fine-print and products view → .product.refund_policy (.refund_period is inherit or none/7/14/30/183, plus .title, .fine_print, .inherited)
- Not every
products write verb is flat: create, update, unpublish, and delete return top-level fields, but covers add, thumbnail set, and content set still wrap their payload in the {success, …, result} envelope — read those under .result
webhooks list → .resource_subscriptions[]
admin users info → .user (includes .user.stripe Stripe Connect state — connected, and when connected stripe_connect_account_id, stripe_dashboard_url, and a verification block of flags/counts or an error subfield when the live Stripe lookup failed — and .user.admin_links impersonate/user/purchases/stripe-dashboard URLs)
admin users social-connections → .social_connections[] (stored verification, currently_linked, timestamps, nullable audience counts, and shared_identity_user_count) and optional .latest_shadow_evaluation (null or missing without a supplied snapshot; otherwise mode: historical_shadow, evaluated_on, recorded_at, stored score, unpaid_balance_cents, would_have_released, hold_source, signals). Historical shadow evidence is not current eligibility or payout authorization.
admin users affiliates → .affiliates[]
admin users comments list → .comments[]
admin users comments add → .comment
admin users compliance → .compliance_info, .info_requests[]
admin users credits add → .user_id, .credit.id, .credit.amount_cents, .credit.reason, .credit.crediting_user_id, .credit.created_at
admin users credits list → .credits[], .pagination.next
admin users radar → .radar_stats, .recent_efws[]
admin users purchases → .purchases[]
admin users related → .related_users[], .truncated, .per_signal_limit
admin users mark-compliant, admin users suspend, admin users suspend-for-tos-violation → .status, .message, .user_id
admin products flag-for-tos-violation → .status, .message, .user_id, .product_id
admin payouts list → .recent_payouts[], .pagination.next. Each payout carries stripe_transfer_id (a po_… payout or py_… destination payment, or null), bank_account (null for PayPal and debit-card payouts; otherwise bank_number routing/BIC, account_holder_full_name, account_type, currency), and trace_id (currently always null).
admin payouts scheduled create → .message, .user_id, .scheduled_payout
admin users refund-balance → .status, .message, .user_id, .count, .total_amount_cents, .currency
admin users refund-all-for-fraud → .success, .user_id, .status (queued), .message, .purchases_to_refund, .block_buyers
admin purchases view → .purchase
admin purchases search → .purchases[], .has_more, .limit
admin purchases lookup → .purchases[]
admin products list → .products[], admin products view → .product
Admin pagination models differ by command:
- Cursor-paginated:
admin users affiliates, admin users comments list, admin users credits list, admin users radar, admin users purchases, and admin purchases lookup return .pagination.next as a cursor string. Pass it back with --cursor.
- Page-paginated:
admin products list returns .pagination.next as an integer page number. Pass it back with --page; use --per-page for page size.
- Capped, not continuable:
admin users related returns at most 50 related users per signal. Always inspect .truncated; when any signal is true, the result hit the cap and there is no cursor/page to fetch the rest.
- Capped, not continuable:
admin purchases search returns .has_more when the server capped results. --limit is server-capped at 25 and there is no continuation token.
Bulk operations
When creating or updating many products:
- Check existing products and permalinks first, then skip duplicates on re-runs.
- Derive custom permalinks deterministically from source data so retries are idempotent.
- Use
--dry-run --json to preview generated requests, and ask the user to confirm before mutating more than 5 products.
- Continue past per-product errors, collect each failure with its product/permalink, and summarize successes and failures at the end.
- For product media failures after creation, retry with the command printed in the error, such as
gumroad products covers add <id> --image ./cover.jpg.
Commands
auth — Manage authentication
# Check auth (do this first if unsure)
gumroad auth status --json --no-input
# Start device authorization and wait for human approval
gumroad auth login --no-input
# Use an existing seller token without browser approval
gumroad auth login --with-token --json --no-input < token.txt
printf '%s\n' "$GUMROAD_ACCESS_TOKEN" | gumroad auth login --with-token --json --no-input
# Print the active resolved seller token for another tool
gumroad auth token --no-input
# Force the local browser OAuth flow
gumroad auth login --web
# Logout
gumroad auth logout --yes --no-input
user — Account info
gumroad user --json --no-input
gumroad user --json --jq '.user.email' --no-input
# Update the seller name and/or bio. Pass an empty value to clear a field.
gumroad user update --name "Jane Doe" --bio "I make great things." --json --no-input
gumroad user update --bio "" --json --no-input
# Custom HTML profile landing page (authored by your agent; no checkout flags).
gumroad user page preview ./landing.html --json --no-input
gumroad user page publish ./landing.html --json --no-input
gumroad user page publish - --json --no-input < landing.html
gumroad user page clear --yes --json --no-input
gumroad user page url --no-input
gumroad user page url --json --jq '.profile_url' --no-input
refund-policy — Store-wide refund policy
# View the current account-level refund policy
gumroad refund-policy view --json --no-input
gumroad refund-policy view --json --jq '.refund_policy.in_effect' --no-input
# Set the refund period. Allowed values: none, 7, 14, 30, 183.
gumroad refund-policy set --period 30 --fine-print "Refund requests are reviewed within 2 business days." --json --no-input
# Clear fine print. This is account-level, not per-product.
gumroad refund-policy set --period none --fine-print "" --json --no-input
admin — Internal admin API
# Admin commands need internal admin auth.
# In agents/CI, set GUMROAD_ADMIN_TOKEN and pass --non-interactive.
# Inspect user identity, sign-in, social, risk, payout, and watchlist state
# Look up by --email, --user-id, or --username (resolves user_id > email > username)
gumroad admin users info --email seller@example.com --json --non-interactive --no-input
gumroad admin users info --username sellerone --json --non-interactive --no-input
# Review affiliate relationships
gumroad admin users affiliates --user-id 2245593582708 --direction granted --limit 50 --json --non-interactive --no-input
gumroad admin users affiliates --username sellerone --direction granted --limit 50 --json --non-interactive --no-input
gumroad admin users affiliates --email seller@example.com --direction received --cursor cur-next --json --non-interactive --no-input
# Read and add admin comments
gumroad admin users comments list --user-id 2245593582708 --type note --limit 50 --json --non-interactive --no-input
gumroad admin users comments list --username sellerone --type note --limit 50 --json --non-interactive --no-input
gumroad admin users comments add --user-id 2245593582708 --content "VAT exempt confirmed" --yes --json --non-interactive --no-input
# Account credits. credits add is a high-stakes write: dry-run first, then issue with explicit --yes.
# Amounts are cents, positive only, capped at $1,000 unless --allow-large-amount is explicitly passed.
gumroad admin users credits list --user-id 2245593582708 --limit 50 --json --non-interactive --no-input
gumroad admin users credits list --username sellerone --limit 50 --json --non-interactive --no-input
gumroad admin users credits add --user-id 2245593582708 --expected-email seller@example.com --amount-cents 1000 --reason "Goodwill for checkout bug" --dry-run --json --non-interactive --no-input
gumroad admin users credits add --user-id 2245593582708 --expected-email seller@example.com --amount-cents 1000 --reason "Goodwill for checkout bug" --yes --json --non-interactive --no-input
# Inspect compliance, Radar risk, and buyer history
gumroad admin users compliance --user-id 2245593582708 --json --non-interactive --no-input
gumroad admin users compliance --username sellerone --json --non-interactive --no-input
gumroad admin users radar --user-id 2245593582708 --limit 50 --json --non-interactive --no-input
gumroad admin users radar --username sellerone --limit 50 --json --non-interactive --no-input
gumroad admin users purchases --user-id 2245593582708 --status successful --has-early-fraud-warning=false --limit 50 --json --non-interactive --no-input
gumroad admin users purchases --username sellerone --status successful --limit 50 --json --non-interactive --no-input
gumroad admin users suspension --username sellerone --json --non-interactive --no-input
# Stored social evidence is optional positive context, never payout approval.
# Check currently_linked and last_verified_at; missing audience counts are unknown.
# Historical SHADOW results describe evaluated_on, never current eligibility or payout authority.
# Missing/null shadow snapshots are not negative scores. --plain remains connection-only;
# use --json or --jq .latest_shadow_evaluation to read the snapshot.
gumroad admin users social-connections --email seller@example.com --json --non-interactive --no-input
# --plain columns: platform, uid, handle, currently_linked, last_verified_at,
# account_created_at, follower_count, post_count, last_posted_at, shared_identity_user_count.
# Historical disconnected rows remain evidence, not a live link. This command does not
# refresh providers or authorize payouts; human mode may show a Stored score from a
# historical shadow snapshot only. Use normal identity/risk review before release.
# Find related accounts by risk signals
gumroad admin users related --email seller@example.com --signal ip --signal payment_address --json --non-interactive --no-input
gumroad admin users related --username sellerone --signal ip --json --non-interactive --no-input
gumroad admin users related --email seller@example.com --json --jq '{related_users, truncated, per_signal_limit}' --non-interactive --no-input
# Mutate user compliance and suspension state.
# mark-compliant never lifts a suspension unless you ask for it. On a suspended
# account a plain mark-compliant is refused with a 422: the server assumes a
# caller that only reviewed the account's finances did not mean to reverse an
# enforcement decision it never looked at. To intentionally restore a suspended
# account, pass --clear-suspension, which is the only thing that sends
# clear_suspension: true.
gumroad admin users mark-compliant --user-id 2245593582708 --expected-email seller@example.com --note "Cleared after review" --yes --json --non-interactive --no-input
gumroad admin users mark-compliant --user-id 2245593582708 --expected-email seller@example.com --note "Suspension lifted after appeal review" --clear-suspension --yes --json --non-interactive --no-input
gumroad admin users suspend --user-id 2245593582708 --expected-email seller@example.com --note "Chargeback risk confirmed" --yes --json --non-interactive --no-input
gumroad admin users suspend-for-tos-violation --user-id 2245593582708 --expected-email seller@example.com --note "DMCA takedown notice confirmed" --yes --json --non-interactive --no-input
gumroad admin products flag-for-tos-violation <product-id> --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input
gumroad admin payouts scheduled create --user-id 2245593582708 --expected-email seller@example.com --processor stripe --payout-date 2026-06-15 --note "Appeal window closes before payout." --yes --json --non-interactive --no-input
gumroad admin payouts scheduled list --status pending --user-id 2245593582708 --json --non-interactive --no-input
# Recent payouts, with Stripe transfer id and destination bank account per row
gumroad admin payouts list --user-id 2245593582708 --limit 25 --json --jq '.recent_payouts[] | {external_id, stripe_transfer_id, bank_account}' --non-interactive --no-input
# Refund-balance dry-run still calls the preview GET, but skips the guarded POST.
gumroad admin users refund-balance --user-id 2245593582708 --expected-email seller@example.com --dry-run --json --non-interactive --no-input
gumroad admin users refund-balance --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input
# Queue fraud refunds for every remaining successful purchase of a suspended seller.
# Requires --expected-email and --expected-count (409 on mismatch or if a run is
# already queued). Buyers are NOT blocked by default; add --block-buyers only for
# buyer-fraud cases (self-purchase / card-testing rings). Refunds run in a
# background job; completion is recorded on the seller's comments and audit log.
gumroad admin users refund-all-for-fraud --user-id 2245593582708 --expected-email seller@example.com --expected-count 18 --yes --json --non-interactive --no-input
gumroad admin users refund-all-for-fraud --user-id 2245593582708 --expected-email seller@example.com --expected-count 18 --block-buyers --yes --json --non-interactive --no-input
# Inspect purchase and product fraud context
gumroad admin purchases view <purchase-id> --with-clusters --json --non-interactive --no-input
gumroad admin purchases search --email buyer@example.com --json --jq '{purchases, has_more, limit}' --non-interactive --no-input
gumroad admin purchases lookup --stripe-fingerprint fp_abc --limit 25 --json --non-interactive --no-input
# Refund a purchase. --reason is required: it is stored on the refund and shown to the
# creator in the "A sale has been refunded" notification email.
gumroad admin purchases refund <purchase-id> --email buyer@example.com --reason "Buyer reported being charged twice" --yes --json --non-interactive --no-input
gumroad admin purchases refund <purchase-id> --email buyer@example.com --amount 5.00 --reason "Partial refund agreed with buyer" --yes --json --non-interactive --no-input
gumroad admin products list --email seller@example.com --page 2 --per-page 25 --json --non-interactive --no-input
gumroad admin products view <product-id> --with-fraud-context --json --non-interactive --no-input
gumroad admin products view <product-id> --json --jq '.product.files[] | [.id, .display_name]' --non-interactive --no-input
gumroad admin products files download <product-id> <file-id> --non-interactive --no-input
# Watchlist state does not pause payouts or change user risk state
gumroad admin users watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 200 --note "Review next buyers" --yes --json --non-interactive --no-input
gumroad admin users update-watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 500 --yes --json --non-interactive --no-input
gumroad admin users update-watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 500 --clear-note --yes --json --non-interactive --no-input
gumroad admin users unwatch --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input
products — Manage products
# List products (paginated)
gumroad products list --json --no-input
gumroad products list --all --json --no-input
gumroad products list --page-key <cursor> --json --no-input
# View a product
gumroad products view <id> --json --no-input
# Find product categories
gumroad products categories --search figma --json --no-input
# See what similar products charge before picking a price
gumroad products comps --category design/ui-and-web/figma --json --no-input
gumroad products comps --category music-and-sound-design --query "whoosh sfx" --json --no-input
# Create a product (created as draft)
gumroad products create --name "Art Pack" --price 10.00 --json --no-input
gumroad products create --name "Figma Kit" --category design/ui-and-web/figma --json --no-input
gumroad products create --name "Art Pack" --price 10.00 --file ./pack.zip --file-name "Art Pack.zip" --json --no-input
gumroad products create --name "Art Pack" --price 10.00 --cover-image ./cover.jpg --thumbnail ./thumb.jpg --json --no-input
gumroad products create --name "Newsletter" --type membership --subscription-duration monthly --json --no-input
gumroad products create --name "E-Book" --type ebook --price 5 --tag art --tag digital --json --no-input
# Update a product
gumroad products update <id> --name "New Name" --json --no-input
gumroad products update <id> --price 15.00 --currency eur --json --no-input
gumroad products update <id> --category design/ui-and-web/figma --json --no-input
gumroad products update <id> --file ./pack.zip --json --no-input
gumroad products update <id> --cover-image ./cover.jpg --json --no-input
gumroad products update <id> --preview-image ./gallery-1.jpg --preview-image ./gallery-2.jpg --json --no-input
gumroad products update <id> --preview-video ./demo.mp4 --json --no-input
gumroad products update <id> --thumbnail ./thumb.jpg --json --no-input
# Product-level refund policy override (only when the account-level policy is off).
# Allowed periods: inherit, none, 7, 14, 30, 183. "inherit" returns to the account default.
gumroad products update <id> --refund-period none --refund-fine-print "No refunds once downloaded." --json --no-input
gumroad products update <id> --refund-period inherit --json --no-input
gumroad products create --name "Art Pack" --price 10 --refund-period none --json --no-input
gumroad products page preview <id> ./landing.html --json --no-input
gumroad products page publish <id> ./landing.html --json --no-input
gumroad products page publish <id> - --json --no-input < landing.html
gumroad products page clear <id> --yes --json --no-input
gumroad products page url <id> --no-input
gumroad products page url <id> --json --jq '.product.landing_url' --no-input
# Product covers and thumbnail
gumroad products covers add <id> --image ./cover.jpg --json --no-input
gumroad products covers add <id> --url https://www.youtube.com/watch?v=qKebcV1jv3A --json --no-input
gumroad products covers reorder <id> <cover_id> <cover_id> --json --no-input
gumroad products covers remove <id> <cover_id> --yes --json --no-input
gumroad products thumbnail set <id> --image ./thumb.jpg --json --no-input
gumroad products thumbnail set <id> --url https://example.com/thumb.png --json --no-input
gumroad products thumbnail remove <id> --yes --json --no-input
# Shared product rich content. `set` replaces the whole page array; dry-run first.
gumroad products content list <id> --json --no-input
gumroad products content get <id> --json --no-input > content.json
gumroad products content set <id> content.json --dry-run --json --no-input
gumroad products content set <id> content.json --yes --json --no-input
gumroad products content get <id> --page <page_id> --json --no-input > page.json
gumroad products content set <id> --page <page_id> --dry-run --json --no-input
gumroad products content set <id> page.json --page <page_id> --dry-run --json --no-input
# Per-variant rich content. `--category` is required because the variant endpoint is category-scoped.
gumroad products content list <id> --variant <variant_id> --category <cat_id> --json --no-input
gumroad products content get <id> --variant <variant_id> --category <cat_id> --json --no-input > content.json
gumroad products content set <id> content.json --variant <variant_id> --category <cat_id> --dry-run --json --no-input
gumroad products content set <id> content.json --variant <variant_id> --category <cat_id> --yes --json --no-input
# Publish / unpublish
gumroad products publish <id> --json --no-input
gumroad products unpublish <id> --json --no-input
# Delete (destructive — needs --yes)
gumroad products delete <id> --yes --json --no-input
# List SKUs for a product
gumroad products skus <id> --json --no-input
In custom HTML, use Gumroad data attributes for live product values and checkout:
<h1 data-gumroad-field="name">Product name</h1>
<span data-gumroad-field="price">$0</span>
<p data-gumroad-field="description">Product description</p>
<a data-gumroad-action="buy">Buy now</a>
<a data-gumroad-action="buy" data-gumroad-option="Pro" data-gumroad-recurrence="yearly">Buy Pro - $99/year</a>
<button data-gumroad-action="buy" data-gumroad-quantity="2">Buy 2 seats</button>
<button data-gumroad-action="buy" data-gumroad-price="19.99">Pay $19.99</button>
List flags: --all, --page-key.
Categories: products categories [--search <term>] returns label, path, and numeric ID. Prefer --category <path> for product create/update. --taxonomy-id remains supported when you already have the numeric ID, but it cannot be combined with --category.
Comps: products comps [--category <path>] [--query <text>] [--currency <iso>] (category or query required) reports the price distribution of comparable priced public products: count of discoverable priced listings (free products excluded), p25/p50/p75 of price_cents in one currency (default usd), and the top five products by sales volume with name, formatted price, and public URL. --category includes descendant categories. Use it to anchor pricing advice in real marketplace numbers instead of static defaults.
Create flags: --name (required), --price, --type (digital|course|ebook|membership|bundle|coffee|call|commission), --currency, --pay-what-you-want, --suggested-price, --description, --custom-summary, --custom-permalink, --custom-receipt, --max-purchase-count, --category, --taxonomy-id, --tag (repeatable), --file (repeatable), --file-name (repeatable, aligned to --file), --file-description (repeatable, aligned to --file), --cover-image, --preview-image (repeatable), --preview-video (repeatable), --thumbnail.
Update flags: --name, --price, --currency, --description, --custom-summary, --custom-permalink, --custom-receipt, --max-purchase-count, --category, --taxonomy-id, --tag (repeatable), --custom-html, --file (repeatable), --file-name, --file-description, --cover-image, --preview-image (repeatable), --preview-video (repeatable), --thumbnail. Prefer products page preview/publish/clear/url for custom HTML page workflows; products update --custom-html remains supported as a low-level product update flag.
Use products update --file for shared product Content. It appends each new file embed to the existing page and leaves prior embeds intact. Use products content get/set for structural content edits. For products with per-variant Content, use variants update ... --file for the specific variant you want to change.
Use --cover-image for the primary cover, repeat --preview-image for additional gallery/preview images, repeat --preview-video for video previews (MP4, MOV, M4V, MPEG, WMV, or WebM), and --thumbnail for the card/library thumbnail. When multiple media flags are combined, covers attach in a fixed order: cover image first, then preview images (in flag order), then preview videos (in flag order) — images and videos cannot be interleaved in a single command. These media flags run the required two-step API flow: direct upload first, then attach by signed blob ID. For an existing product, products thumbnail set --url asks Gumroad to download and attach a public HTTP(S) image directly.
files — Upload and recover file attachments
# Upload a file and print the canonical Gumroad URL
gumroad files upload ./pack.zip --json --no-input
gumroad files upload ./pack.zip --name "Art Pack.zip" --json --no-input
# Finalize a saved recovery manifest after a state-unknown upload
gumroad files complete --recovery recovery.json --yes --json --no-input
jq '.error.recovery' err.json | gumroad files complete --recovery - --yes --json --no-input
# Abort an orphaned multipart upload
gumroad files abort --upload-id up-123 --key attachments/u/k/original/pack.zip --yes --json --no-input
files upload and files complete both return .file_url. When a JSON upload fails with recovery details, reuse .error.recovery with files complete to finish it or files abort to reclaim the orphaned multipart upload.
media — Public media library (page images)
Files from files upload are stored privately and can never be displayed on custom product landing pages or profile pages — the pages' Content-Security-Policy only allows Gumroad's public CDN, and page moderation cannot fetch private URLs, so a page embedding one fails review every time. To put an image on a page, upload it to the public media library instead and embed the returned .media.url in the page HTML before pages push / products page publish.
# Host an image publicly and print its page-embeddable URL
gumroad media upload ./logo.png --json --no-input
gumroad media upload ./logo.png --name "Store logo" --json --jq '.media.url' --no-input
# List and delete media library files
gumroad media list --json --no-input
gumroad media delete k3n8xq1p9wr2sd4a --yes --json --no-input
The CLI detects JPEG, PNG, GIF, WebP, BMP, and ICO images up to 10 MB. It rejects SVG and other formats it cannot identify locally. Gumroad checks each image again and moderates it before hosting. A flagged image fails with the moderation message. Deleting a file breaks each page that still embeds its URL. The upload command requires the edit_profile scope. The list command requires the view_profile scope. Tokens created before the CLI requested these scopes fail with Access denied: This endpoint requires the view_profile scope. Run gumroad auth login again to create a token with the new scopes.
If an upload returns media_commit_state_unknown, do not retry it automatically. Read .error.recovery.key. List the media and find the item whose URL contains that key. If the item exists, the upload completed. If it does not exist, keep .error.recovery.signed_blob_id and .error.recovery.key for support. If an upload returns media_direct_upload_state_unknown, do not start a new upload. Keep the same recovery values for support. If it returns media_output_failed, the upload completed. Use .error.recovery.media_id and .error.recovery.media_url. Do not retry the upload. If a delete returns media_delete_output_failed, the deletion completed. Do not retry it. If a delete returns media_delete_state_unknown, list the media and search for .error.recovery.media_id. Do not retry the deletion automatically.
emails — Manage audience emails
# Create a draft from an HTML body file (or - for stdin). Draft is the default safety behavior.
gumroad emails create --subject "New release" --body ./email.html --json --no-input
gumroad emails create --subject "Product update" --body ./email.html --audience product --product <id> --json --no-input
# Preview before sending; use `.preview_url` for human review.
gumroad emails send-preview <id> --json --jq '.preview_url' --no-input
gumroad emails view <id> --json --no-input
# List drafts, scheduled emails, or sent emails.
gumroad emails list --state dra
…(truncated)
1---2name: gumroad3description: Use the `gumroad` CLI to look up and manage Gumroad data from the terminal. Trigger when the user asks about Gumroad products, files, file uploads, attachments, sales, subscribers, licenses, payouts, audience emails, email workflows, broadcasts, offer codes, webhooks, refund policies, or any Gumroad data lookup. Also trigger on "check my Gumroad", "look up a sale", "verify a license", "list my products", "how much have I made", "who bought", "recent sales", "refund a sale", "create a product", "upload a file", "attach a file to a product", "add a cover image", "set a product thumbnail", "get product content", "set product content", "upload product media", "publish a product landing page", "publish custom HTML", "clear custom HTML", "customize my profile page", "publish a profile landing page", "set profile custom HTML", "attach a file to a variant", "finish a failed upload", "abort an upload", "manage webhooks", "draft an email", "preview a broadcast", "send an audience email", "list drafts", "set refund poli4---56# gumroad CLI78Use `gumroad` (Gumroad CLI) to query and manage Gumroad data.910## Agent invariants1112Always follow these rules:1314- **Always** pass `--no-input` to prevent interactive prompts from blocking.15- **Always** pass `--json` for programmatic access.16- Use `--json --jq <expr>` together to extract exactly what you need.17- For operations that can prompt for confirmation (delete, refund, workflow step adds, workflow delay changes, mutating admin actions, `files abort`, `files complete` replay, product updates that remove files, or `products content set` when omitted page IDs will be deleted), add `--yes` to skip confirmation.18- Pass `--quiet` to suppress spinners and status messages.19- Pass `--dry-run` to preview mutating requests without executing them.20- Use `--page-delay 200ms` with `--all` to avoid rate limits on large datasets.21- Prices are in whole currency units (e.g. `--price 10.00` for $10), not cents. The CLI converts internally. Use `--currency eur` to change currency.22- Products are created as drafts — use `gumroad products publish <id>` to make them live.23- Product cover and thumbnail uploads support JPEG, PNG, and GIF. WebP is not supported by the API and the CLI rejects it before upload.24- Product custom HTML landing pages use `gumroad products page preview <id> ./landing.html` to run the backend sanitizer without writing, `gumroad products page publish <id> ./landing.html` to store the page, `gumroad products page clear <id> --yes` to remove it, and `gumroad products page url <id>` to print the live URL. `--dry-run` only previews the CLI request body; it does not call the backend sanitizer. Inspect `.sanitization_report` in `preview` and `publish` JSON output for server-side changes.25- Profile custom HTML landing pages mirror the product commands without a product id and without checkout: `gumroad user page preview ./landing.html`, `gumroad user page publish ./landing.html` (read from stdin with `-`), `gumroad user page clear --yes`, and `gumroad user page url` (prints the public profile URL and its `/landing/embed` URL). A profile has no buy button, so omit `data-gumroad-action="buy"` and the checkout data attributes; link to products instead.26- Storefront pages (slugged pages serving at `<username>.gumroad.com/<slug>`) use `gumroad pages list`, `gumroad pages create --title <title> [--slug <slug>] [path]` (with an HTML path or `-` the page is created as custom HTML; without one it starts empty for the in-app editor), `gumroad pages pull <slug>` to download a page's existing custom HTML so pull → edit → push is a real round trip (writes `<slug>.html`; `-o <path>` to choose, `-o -` for stdout; refuses to overwrite without `--force`; errors with a hint when the page has no custom HTML), `gumroad pages scaffold <slug>` to generate starter HTML for a rich-text page or a default profile from a static snapshot of its current render (same flags as pull; pushing the scaffold converts the page to custom HTML and replaces the dynamic storefront/editor experience), `gumroad pages push <slug> ./page.html` to replace a page with custom HTML, and `gumroad pages preview ./page.html` to run the backend sanitizer without publishing. The loop for going custom: `pull` (or `scaffold` when there's no custom HTML yet) → edit → `preview` → `push`. `--json`/`--jq` on `pull`/`scaffold` still write the file and additionally print the raw API response; combining them with `-o -` is rejected since both would own stdout. The special slug `profile` targets the profile landing page (`pull profile` downloads your published custom HTML, `scaffold profile` snapshots the default storefront render when none is published; push uses the same endpoints as `user page publish`). Custom HTML pages require the seller's `custom_html_pages` feature to be enabled; without it writes fail with an access message. Writes to slugged pages (`pages create`/`pages push <slug>`) also require the token to carry the `edit_profile` scope — tokens minted before the CLI requested that scope get a 403 telling them to re-run `gumroad auth login`.27- Product rich content uses `gumroad products content list <id> --json --no-input` to inspect page IDs, `gumroad products content get <id> --json --no-input` to dump the shared `rich_content` page array, and `gumroad products content set <id> content.json --dry-run --json --no-input` to preview a whole-document replacement. Without an explicit path, whole-document `set` reads `./content.json`; `set --page` reads `./page.json`. Use `--page <page_id>` with `get`/`set` to edit one matching page object; `set --page` still sends a merged whole-document PUT. For per-variant content, pass both `--variant <variant_id>` and `--category <cat_id>`. Whole-document `set` deletes existing pages omitted from the JSON.28- Custom HTML pages can use `data-gumroad-field="name"`, `data-gumroad-field="price"`, `data-gumroad-field="description"`, and `data-gumroad-action="buy"`. To preselect checkout state, add `data-gumroad-option="<variant name>"`, `data-gumroad-quantity="<integer>"`, `data-gumroad-price="<decimal>"`, or `data-gumroad-recurrence="monthly|quarterly|biannually|yearly|every_two_years"`. Production validates these values and falls back to product defaults when invalid. Prefer anchors for buy CTAs so production can add a checkout href; non-anchor buy elements also post to checkout.29- Audience emails are created as drafts by default. Use `gumroad emails send-preview <id> --json --no-input` and inspect `.preview_url` before `gumroad emails send <id> --yes --json --no-input`. Creating with `--send` publishes and blasts immediately, so use `--dry-run` first and require explicit human approval. To schedule a send instead of blasting immediately, create the draft then `gumroad emails schedule <id> --at "<RFC3339>" --json --no-input`; `gumroad emails unschedule <id>` returns a scheduled email to draft.30- Workflow email writes do not change the workflow publication state. Adding a step to a published workflow can schedule eligible past recipients. Changing a delay can reschedule recipients. Use `--dry-run` before each write.31- If a command fails with a seller auth error, run `gumroad auth status --json --no-input` first. Agents can start seller auth with `gumroad auth login --no-input` and hand the printed approval URL to a human, or use an existing seller token via `GUMROAD_ACCESS_TOKEN` or `gumroad auth login --with-token`.32- For admin commands in agents/CI, pass `--non-interactive` and set `GUMROAD_ADMIN_TOKEN`; interactive shells can store an admin token with `gumroad auth login --web`.3334## Connect from Claude Desktop / Cursor / Claude Code3536- Run `gumroad mcp` to serve the public CLI commands as MCP tools over stdio.37- Log in first with `gumroad auth login`, or pass `GUMROAD_ACCESS_TOKEN` in the MCP server environment. The server starts without a token, but calls return a login hint until credentials are available.3839Add this to your client's MCP configuration (use the absolute path to `gumroad` if it is not on the client's PATH):4041```json42{"mcpServers":{"gumroad":{"command":"gumroad","args":["mcp"]}}}43```4445Tools follow CLI leaf command paths with underscores, including hyphens converted to underscores: `products_list`, `products_view`, `offer_codes_list`, `sales_refund`. Input keys are the original long flag names; repeatable flags take arrays of strings, and positional arguments go in `args` (for example, `{"args":["<product-id>"]}`). Defaults stay with the CLI, and its validators report missing arguments or flags. Runnable groups such as `gumroad user` are exposed too (`user` reads the account); use `user` or `products_list` as a connection check.4647Every call uses a fresh command with `--json --no-input --quiet` and automatically passes `--yes` where available. Mutations run immediately, without interactive confirmation; require approval in the MCP client before sending a mutating call. Use `dry-run: true` when supported to preview requests. Auth, admin, completion, skill, help, MCP itself, and hidden/deprecated commands are excluded. The server uses only the seller token, never the admin token.4849Only connect trusted clients: tools have the same local file access as the CLI, including uploads and downloads. File paths are on the machine running the server. Stdin-based content input is unavailable; provide file paths or explicit flags instead. HTTP transport is not supported. Annotations are conservative: list/view/get/preview/pull/download-style commands are marked read-only; every other tool (including `licenses_verify`, which increments uses unless `no-increment` is true, `pages_push` and `emails_send`) carries `destructiveHint` so clients ask before running it. They describe the command category, not a security boundary (downloads still write local files).5051## Response shapes5253Most responses are wrapped in `{"success": true, ...}` with resource-specific keys:5455- `user` → `.user`, `user update` → `.user`56- `user page preview` → `.custom_html`, `.sanitization_report`57- `user page publish` / `user page clear` → `.custom_html`, `.previous_custom_html`, `.profile_url`, `.sanitization_report`58- `user page url` → `.profile_url`, `.has_landing_page`59- `pages list` → `.pages[]` (`.slug`, `.title`, `.content`, `.custom_html`, `.url`); `pages create` / `pages push <slug>` → `.page`; `pages pull <slug>` / `pages scaffold <slug>` → `.page` + `.rendered_html` (pull writes the page's `.page.custom_html`; scaffold writes `.rendered_html`, the static render snapshot); `pages pull profile` / `pages scaffold profile` → `.custom_html`, `.rendered_html`, `.has_landing_page`, `.profile_url`; `pages push profile` → profile shape (`.custom_html`, `.previous_custom_html`, `.profile_url`, `.sanitization_report`); `pages preview` → `.custom_html`, `.sanitization_report`60- `refund-policy view/set` → `.refund_policy`61- `products list` → `.products[]`62- `products view` → `.product`63- `products content get` → rich content page array directly, or one page object with `--page`64- `products content list` → rich content page summary array directly65- `products content set` → mutation envelope with `.result`66- `sales list` → `.sales[]`67- `sales buyers` → `.buyers[]` (`email`, `name`, `purchase_count`, `last_purchase_date`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`)68- `sales view` → `.sale` (includes `.currency`, the ISO code the sale is priced in — the same currency a refund amount is read in)69- `sales export` → `.status`, `.recipient_email`70- `sales summary` → `.gross_cents`, `.net_cents`, `.breakdown[]`71- `emails list` → `.emails[]`, `emails view/create/send/schedule/unschedule` → `.email`, `emails send-preview` → `.preview_url`, `emails delete` → `.message`72- `workflows list` → `.workflows[]`, `workflows view` → `.workflow`, `workflows add-email/update-email` → `.email`73- `payouts list` → `.payouts[]`, `payouts view/upcoming` → `.payout`74- `subscribers list` → `.subscribers[]`, `subscribers view` → `.subscriber`75- `licenses verify` → `.purchase`76- `offer-codes list` → `.offer_codes[]`77- `upsells list` → `.upsells[]`, `upsells view/create/update` → `.upsell`, `upsells delete` → `.message`78- `variant-categories list` → `.variant_categories[]`79- `variants list` → `.variants[]`80- `files upload` / `files complete` → `.file_url`81- `media upload` → `.media` (`.id`, `.name`, `.url`, `.file_size`), `media list` → `.media[]`, `media delete` → `.message`82- `products create` with media flags → `.product` plus `.media[]`83- `products update` with media flags → `.product` plus `.media[]`84- `products covers add --image` → `.result.covers[]`, `.result.main_cover_id`, plus `.result.media[]`85- `products covers add --url` → `.result.covers[]`, `.result.main_cover_id`86- `products thumbnail set --image` → `.result.thumbnail`, plus `.result.media[]`87- `products thumbnail set --url` → `.result.thumbnail`88- `products page preview` → `.custom_html`, `.sanitization_report`89- `products page publish` / `products page clear` → `.product.custom_html`, `.product.landing_url`, `.previous_custom_html`, `.sanitization_report`90- `products update --custom-html` → `.product.custom_html`, `.product.landing_url`, `.previous_custom_html`, `.sanitization_report`91- `products create/update --refund-period/--refund-fine-print` and `products view` → `.product.refund_policy` (`.refund_period` is `inherit` or `none/7/14/30/183`, plus `.title`, `.fine_print`, `.inherited`)92- Not every `products` write verb is flat: `create`, `update`, `unpublish`, and `delete` return top-level fields, but `covers add`, `thumbnail set`, and `content set` still wrap their payload in the `{success, …, result}` envelope — read those under `.result`93- `webhooks list` → `.resource_subscriptions[]`94- `admin users info` → `.user` (includes `.user.stripe` Stripe Connect state — `connected`, and when connected `stripe_connect_account_id`, `stripe_dashboard_url`, and a `verification` block of flags/counts or an `error` subfield when the live Stripe lookup failed — and `.user.admin_links` impersonate/user/purchases/stripe-dashboard URLs)95- `admin users social-connections` → `.social_connections[]` (stored verification, `currently_linked`, timestamps, nullable audience counts, and `shared_identity_user_count`) and optional `.latest_shadow_evaluation` (null or missing without a supplied snapshot; otherwise `mode: historical_shadow`, `evaluated_on`, `recorded_at`, stored `score`, `unpaid_balance_cents`, `would_have_released`, `hold_source`, `signals`). Historical shadow evidence is not current eligibility or payout authorization.96- `admin users affiliates` → `.affiliates[]`97- `admin users comments list` → `.comments[]`98- `admin users comments add` → `.comment`99- `admin users compliance` → `.compliance_info`, `.info_requests[]`100- `admin users credits add` → `.user_id`, `.credit.id`, `.credit.amount_cents`, `.credit.reason`, `.credit.crediting_user_id`, `.credit.created_at`101- `admin users credits list` → `.credits[]`, `.pagination.next`102- `admin users radar` → `.radar_stats`, `.recent_efws[]`103- `admin users purchases` → `.purchases[]`104- `admin users related` → `.related_users[]`, `.truncated`, `.per_signal_limit`105- `admin users mark-compliant`, `admin users suspend`, `admin users suspend-for-tos-violation` → `.status`, `.message`, `.user_id`106- `admin products flag-for-tos-violation` → `.status`, `.message`, `.user_id`, `.product_id`107- `admin payouts list` → `.recent_payouts[]`, `.pagination.next`. Each payout carries `stripe_transfer_id` (a `po_…` payout or `py_…` destination payment, or null), `bank_account` (null for PayPal and debit-card payouts; otherwise `bank_number` routing/BIC, `account_holder_full_name`, `account_type`, `currency`), and `trace_id` (currently always null).108- `admin payouts scheduled create` → `.message`, `.user_id`, `.scheduled_payout`109- `admin users refund-balance` → `.status`, `.message`, `.user_id`, `.count`, `.total_amount_cents`, `.currency`110- `admin users refund-all-for-fraud` → `.success`, `.user_id`, `.status` (`queued`), `.message`, `.purchases_to_refund`, `.block_buyers`111- `admin purchases view` → `.purchase`112- `admin purchases search` → `.purchases[]`, `.has_more`, `.limit`113- `admin purchases lookup` → `.purchases[]`114- `admin products list` → `.products[]`, `admin products view` → `.product`115116Admin pagination models differ by command:117118- Cursor-paginated: `admin users affiliates`, `admin users comments list`, `admin users credits list`, `admin users radar`, `admin users purchases`, and `admin purchases lookup` return `.pagination.next` as a cursor string. Pass it back with `--cursor`.119- Page-paginated: `admin products list` returns `.pagination.next` as an integer page number. Pass it back with `--page`; use `--per-page` for page size.120- Capped, not continuable: `admin users related` returns at most 50 related users per signal. Always inspect `.truncated`; when any signal is `true`, the result hit the cap and there is no cursor/page to fetch the rest.121- Capped, not continuable: `admin purchases search` returns `.has_more` when the server capped results. `--limit` is server-capped at 25 and there is no continuation token.122123## Bulk operations124125When creating or updating many products:126127- Check existing products and permalinks first, then skip duplicates on re-runs.128- Derive custom permalinks deterministically from source data so retries are idempotent.129- Use `--dry-run --json` to preview generated requests, and ask the user to confirm before mutating more than 5 products.130- Continue past per-product errors, collect each failure with its product/permalink, and summarize successes and failures at the end.131- For product media failures after creation, retry with the command printed in the error, such as `gumroad products covers add <id> --image ./cover.jpg`.132133## Commands134135### auth — Manage authentication136137```sh138# Check auth (do this first if unsure)139gumroad auth status --json --no-input140141# Start device authorization and wait for human approval142gumroad auth login --no-input143144# Use an existing seller token without browser approval145gumroad auth login --with-token --json --no-input < token.txt146printf '%s\n' "$GUMROAD_ACCESS_TOKEN" | gumroad auth login --with-token --json --no-input147148# Print the active resolved seller token for another tool149gumroad auth token --no-input150151# Force the local browser OAuth flow152gumroad auth login --web153154# Logout155gumroad auth logout --yes --no-input156```157158### user — Account info159160```sh161gumroad user --json --no-input162gumroad user --json --jq '.user.email' --no-input163164# Update the seller name and/or bio. Pass an empty value to clear a field.165gumroad user update --name "Jane Doe" --bio "I make great things." --json --no-input166gumroad user update --bio "" --json --no-input167168# Custom HTML profile landing page (authored by your agent; no checkout flags).169gumroad user page preview ./landing.html --json --no-input170gumroad user page publish ./landing.html --json --no-input171gumroad user page publish - --json --no-input < landing.html172gumroad user page clear --yes --json --no-input173gumroad user page url --no-input174gumroad user page url --json --jq '.profile_url' --no-input175```176177### refund-policy — Store-wide refund policy178179```sh180# View the current account-level refund policy181gumroad refund-policy view --json --no-input182gumroad refund-policy view --json --jq '.refund_policy.in_effect' --no-input183184# Set the refund period. Allowed values: none, 7, 14, 30, 183.185gumroad refund-policy set --period 30 --fine-print "Refund requests are reviewed within 2 business days." --json --no-input186187# Clear fine print. This is account-level, not per-product.188gumroad refund-policy set --period none --fine-print "" --json --no-input189```190191### admin — Internal admin API192193```sh194# Admin commands need internal admin auth.195# In agents/CI, set GUMROAD_ADMIN_TOKEN and pass --non-interactive.196197# Inspect user identity, sign-in, social, risk, payout, and watchlist state198# Look up by --email, --user-id, or --username (resolves user_id > email > username)199gumroad admin users info --email seller@example.com --json --non-interactive --no-input200gumroad admin users info --username sellerone --json --non-interactive --no-input201202# Review affiliate relationships203gumroad admin users affiliates --user-id 2245593582708 --direction granted --limit 50 --json --non-interactive --no-input204gumroad admin users affiliates --username sellerone --direction granted --limit 50 --json --non-interactive --no-input205gumroad admin users affiliates --email seller@example.com --direction received --cursor cur-next --json --non-interactive --no-input206207# Read and add admin comments208gumroad admin users comments list --user-id 2245593582708 --type note --limit 50 --json --non-interactive --no-input209gumroad admin users comments list --username sellerone --type note --limit 50 --json --non-interactive --no-input210gumroad admin users comments add --user-id 2245593582708 --content "VAT exempt confirmed" --yes --json --non-interactive --no-input211212# Account credits. credits add is a high-stakes write: dry-run first, then issue with explicit --yes.213# Amounts are cents, positive only, capped at $1,000 unless --allow-large-amount is explicitly passed.214gumroad admin users credits list --user-id 2245593582708 --limit 50 --json --non-interactive --no-input215gumroad admin users credits list --username sellerone --limit 50 --json --non-interactive --no-input216gumroad admin users credits add --user-id 2245593582708 --expected-email seller@example.com --amount-cents 1000 --reason "Goodwill for checkout bug" --dry-run --json --non-interactive --no-input217gumroad admin users credits add --user-id 2245593582708 --expected-email seller@example.com --amount-cents 1000 --reason "Goodwill for checkout bug" --yes --json --non-interactive --no-input218219# Inspect compliance, Radar risk, and buyer history220gumroad admin users compliance --user-id 2245593582708 --json --non-interactive --no-input221gumroad admin users compliance --username sellerone --json --non-interactive --no-input222gumroad admin users radar --user-id 2245593582708 --limit 50 --json --non-interactive --no-input223gumroad admin users radar --username sellerone --limit 50 --json --non-interactive --no-input224gumroad admin users purchases --user-id 2245593582708 --status successful --has-early-fraud-warning=false --limit 50 --json --non-interactive --no-input225gumroad admin users purchases --username sellerone --status successful --limit 50 --json --non-interactive --no-input226gumroad admin users suspension --username sellerone --json --non-interactive --no-input227228# Stored social evidence is optional positive context, never payout approval.229# Check currently_linked and last_verified_at; missing audience counts are unknown.230# Historical SHADOW results describe evaluated_on, never current eligibility or payout authority.231# Missing/null shadow snapshots are not negative scores. --plain remains connection-only;232# use --json or --jq .latest_shadow_evaluation to read the snapshot.233gumroad admin users social-connections --email seller@example.com --json --non-interactive --no-input234# --plain columns: platform, uid, handle, currently_linked, last_verified_at,235# account_created_at, follower_count, post_count, last_posted_at, shared_identity_user_count.236# Historical disconnected rows remain evidence, not a live link. This command does not237# refresh providers or authorize payouts; human mode may show a Stored score from a238# historical shadow snapshot only. Use normal identity/risk review before release.239240# Find related accounts by risk signals241gumroad admin users related --email seller@example.com --signal ip --signal payment_address --json --non-interactive --no-input242gumroad admin users related --username sellerone --signal ip --json --non-interactive --no-input243gumroad admin users related --email seller@example.com --json --jq '{related_users, truncated, per_signal_limit}' --non-interactive --no-input244245# Mutate user compliance and suspension state.246# mark-compliant never lifts a suspension unless you ask for it. On a suspended247# account a plain mark-compliant is refused with a 422: the server assumes a248# caller that only reviewed the account's finances did not mean to reverse an249# enforcement decision it never looked at. To intentionally restore a suspended250# account, pass --clear-suspension, which is the only thing that sends251# clear_suspension: true.252gumroad admin users mark-compliant --user-id 2245593582708 --expected-email seller@example.com --note "Cleared after review" --yes --json --non-interactive --no-input253gumroad admin users mark-compliant --user-id 2245593582708 --expected-email seller@example.com --note "Suspension lifted after appeal review" --clear-suspension --yes --json --non-interactive --no-input254gumroad admin users suspend --user-id 2245593582708 --expected-email seller@example.com --note "Chargeback risk confirmed" --yes --json --non-interactive --no-input255gumroad admin users suspend-for-tos-violation --user-id 2245593582708 --expected-email seller@example.com --note "DMCA takedown notice confirmed" --yes --json --non-interactive --no-input256gumroad admin products flag-for-tos-violation <product-id> --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input257gumroad admin payouts scheduled create --user-id 2245593582708 --expected-email seller@example.com --processor stripe --payout-date 2026-06-15 --note "Appeal window closes before payout." --yes --json --non-interactive --no-input258gumroad admin payouts scheduled list --status pending --user-id 2245593582708 --json --non-interactive --no-input259260# Recent payouts, with Stripe transfer id and destination bank account per row261gumroad admin payouts list --user-id 2245593582708 --limit 25 --json --jq '.recent_payouts[] | {external_id, stripe_transfer_id, bank_account}' --non-interactive --no-input262263# Refund-balance dry-run still calls the preview GET, but skips the guarded POST.264gumroad admin users refund-balance --user-id 2245593582708 --expected-email seller@example.com --dry-run --json --non-interactive --no-input265gumroad admin users refund-balance --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input266267# Queue fraud refunds for every remaining successful purchase of a suspended seller.268# Requires --expected-email and --expected-count (409 on mismatch or if a run is269# already queued). Buyers are NOT blocked by default; add --block-buyers only for270# buyer-fraud cases (self-purchase / card-testing rings). Refunds run in a271# background job; completion is recorded on the seller's comments and audit log.272gumroad admin users refund-all-for-fraud --user-id 2245593582708 --expected-email seller@example.com --expected-count 18 --yes --json --non-interactive --no-input273gumroad admin users refund-all-for-fraud --user-id 2245593582708 --expected-email seller@example.com --expected-count 18 --block-buyers --yes --json --non-interactive --no-input274275# Inspect purchase and product fraud context276gumroad admin purchases view <purchase-id> --with-clusters --json --non-interactive --no-input277gumroad admin purchases search --email buyer@example.com --json --jq '{purchases, has_more, limit}' --non-interactive --no-input278gumroad admin purchases lookup --stripe-fingerprint fp_abc --limit 25 --json --non-interactive --no-input279280# Refund a purchase. --reason is required: it is stored on the refund and shown to the281# creator in the "A sale has been refunded" notification email.282gumroad admin purchases refund <purchase-id> --email buyer@example.com --reason "Buyer reported being charged twice" --yes --json --non-interactive --no-input283gumroad admin purchases refund <purchase-id> --email buyer@example.com --amount 5.00 --reason "Partial refund agreed with buyer" --yes --json --non-interactive --no-input284gumroad admin products list --email seller@example.com --page 2 --per-page 25 --json --non-interactive --no-input285gumroad admin products view <product-id> --with-fraud-context --json --non-interactive --no-input286287gumroad admin products view <product-id> --json --jq '.product.files[] | [.id, .display_name]' --non-interactive --no-input288gumroad admin products files download <product-id> <file-id> --non-interactive --no-input289# Watchlist state does not pause payouts or change user risk state290gumroad admin users watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 200 --note "Review next buyers" --yes --json --non-interactive --no-input291gumroad admin users update-watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 500 --yes --json --non-interactive --no-input292gumroad admin users update-watch --user-id 2245593582708 --expected-email seller@example.com --revenue-threshold 500 --clear-note --yes --json --non-interactive --no-input293gumroad admin users unwatch --user-id 2245593582708 --expected-email seller@example.com --yes --json --non-interactive --no-input294```295296### products — Manage products297298```sh299# List products (paginated)300gumroad products list --json --no-input301gumroad products list --all --json --no-input302gumroad products list --page-key <cursor> --json --no-input303304# View a product305gumroad products view <id> --json --no-input306307# Find product categories308gumroad products categories --search figma --json --no-input309310# See what similar products charge before picking a price311gumroad products comps --category design/ui-and-web/figma --json --no-input312gumroad products comps --category music-and-sound-design --query "whoosh sfx" --json --no-input313314# Create a product (created as draft)315gumroad products create --name "Art Pack" --price 10.00 --json --no-input316gumroad products create --name "Figma Kit" --category design/ui-and-web/figma --json --no-input317gumroad products create --name "Art Pack" --price 10.00 --file ./pack.zip --file-name "Art Pack.zip" --json --no-input318gumroad products create --name "Art Pack" --price 10.00 --cover-image ./cover.jpg --thumbnail ./thumb.jpg --json --no-input319gumroad products create --name "Newsletter" --type membership --subscription-duration monthly --json --no-input320gumroad products create --name "E-Book" --type ebook --price 5 --tag art --tag digital --json --no-input321322# Update a product323gumroad products update <id> --name "New Name" --json --no-input324gumroad products update <id> --price 15.00 --currency eur --json --no-input325gumroad products update <id> --category design/ui-and-web/figma --json --no-input326gumroad products update <id> --file ./pack.zip --json --no-input327gumroad products update <id> --cover-image ./cover.jpg --json --no-input328gumroad products update <id> --preview-image ./gallery-1.jpg --preview-image ./gallery-2.jpg --json --no-input329gumroad products update <id> --preview-video ./demo.mp4 --json --no-input330gumroad products update <id> --thumbnail ./thumb.jpg --json --no-input331332# Product-level refund policy override (only when the account-level policy is off).333# Allowed periods: inherit, none, 7, 14, 30, 183. "inherit" returns to the account default.334gumroad products update <id> --refund-period none --refund-fine-print "No refunds once downloaded." --json --no-input335gumroad products update <id> --refund-period inherit --json --no-input336gumroad products create --name "Art Pack" --price 10 --refund-period none --json --no-input337gumroad products page preview <id> ./landing.html --json --no-input338gumroad products page publish <id> ./landing.html --json --no-input339gumroad products page publish <id> - --json --no-input < landing.html340gumroad products page clear <id> --yes --json --no-input341gumroad products page url <id> --no-input342gumroad products page url <id> --json --jq '.product.landing_url' --no-input343344# Product covers and thumbnail345gumroad products covers add <id> --image ./cover.jpg --json --no-input346gumroad products covers add <id> --url https://www.youtube.com/watch?v=qKebcV1jv3A --json --no-input347gumroad products covers reorder <id> <cover_id> <cover_id> --json --no-input348gumroad products covers remove <id> <cover_id> --yes --json --no-input349gumroad products thumbnail set <id> --image ./thumb.jpg --json --no-input350gumroad products thumbnail set <id> --url https://example.com/thumb.png --json --no-input351gumroad products thumbnail remove <id> --yes --json --no-input352353# Shared product rich content. `set` replaces the whole page array; dry-run first.354gumroad products content list <id> --json --no-input355gumroad products content get <id> --json --no-input > content.json356gumroad products content set <id> content.json --dry-run --json --no-input357gumroad products content set <id> content.json --yes --json --no-input358gumroad products content get <id> --page <page_id> --json --no-input > page.json359gumroad products content set <id> --page <page_id> --dry-run --json --no-input360gumroad products content set <id> page.json --page <page_id> --dry-run --json --no-input361362# Per-variant rich content. `--category` is required because the variant endpoint is category-scoped.363gumroad products content list <id> --variant <variant_id> --category <cat_id> --json --no-input364gumroad products content get <id> --variant <variant_id> --category <cat_id> --json --no-input > content.json365gumroad products content set <id> content.json --variant <variant_id> --category <cat_id> --dry-run --json --no-input366gumroad products content set <id> content.json --variant <variant_id> --category <cat_id> --yes --json --no-input367368# Publish / unpublish369gumroad products publish <id> --json --no-input370gumroad products unpublish <id> --json --no-input371372# Delete (destructive — needs --yes)373gumroad products delete <id> --yes --json --no-input374375# List SKUs for a product376gumroad products skus <id> --json --no-input377378```379380In custom HTML, use Gumroad data attributes for live product values and checkout:381382```html383<h1 data-gumroad-field="name">Product name</h1>384<span data-gumroad-field="price">$0</span>385<p data-gumroad-field="description">Product description</p>386<a data-gumroad-action="buy">Buy now</a>387<a data-gumroad-action="buy" data-gumroad-option="Pro" data-gumroad-recurrence="yearly">Buy Pro - $99/year</a>388<button data-gumroad-action="buy" data-gumroad-quantity="2">Buy 2 seats</button>389<button data-gumroad-action="buy" data-gumroad-price="19.99">Pay $19.99</button>390```391392**List flags:** `--all`, `--page-key`.393394**Categories:** `products categories [--search <term>]` returns label, path, and numeric ID. Prefer `--category <path>` for product create/update. `--taxonomy-id` remains supported when you already have the numeric ID, but it cannot be combined with `--category`.395396**Comps:** `products comps [--category <path>] [--query <text>] [--currency <iso>]` (category or query required) reports the price distribution of comparable priced public products: count of discoverable priced listings (free products excluded), p25/p50/p75 of `price_cents` in one currency (default `usd`), and the top five products by sales volume with name, formatted price, and public URL. `--category` includes descendant categories. Use it to anchor pricing advice in real marketplace numbers instead of static defaults.397398**Create flags:** `--name` (required), `--price`, `--type` (digital|course|ebook|membership|bundle|coffee|call|commission), `--currency`, `--pay-what-you-want`, `--suggested-price`, `--description`, `--custom-summary`, `--custom-permalink`, `--custom-receipt`, `--max-purchase-count`, `--category`, `--taxonomy-id`, `--tag` (repeatable), `--file` (repeatable), `--file-name` (repeatable, aligned to `--file`), `--file-description` (repeatable, aligned to `--file`), `--cover-image`, `--preview-image` (repeatable), `--preview-video` (repeatable), `--thumbnail`.399400**Update flags:** `--name`, `--price`, `--currency`, `--description`, `--custom-summary`, `--custom-permalink`, `--custom-receipt`, `--max-purchase-count`, `--category`, `--taxonomy-id`, `--tag` (repeatable), `--custom-html`, `--file` (repeatable), `--file-name`, `--file-description`, `--cover-image`, `--preview-image` (repeatable), `--preview-video` (repeatable), `--thumbnail`. Prefer `products page preview/publish/clear/url` for custom HTML page workflows; `products update --custom-html` remains supported as a low-level product update flag.401402Use `products update --file` for shared product Content. It appends each new file embed to the existing page and leaves prior embeds intact. Use `products content get/set` for structural content edits. For products with per-variant Content, use `variants update ... --file` for the specific variant you want to change.403404Use `--cover-image` for the primary cover, repeat `--preview-image` for additional gallery/preview images, repeat `--preview-video` for video previews (MP4, MOV, M4V, MPEG, WMV, or WebM), and `--thumbnail` for the card/library thumbnail. When multiple media flags are combined, covers attach in a fixed order: cover image first, then preview images (in flag order), then preview videos (in flag order) — images and videos cannot be interleaved in a single command. These media flags run the required two-step API flow: direct upload first, then attach by signed blob ID. For an existing product, `products thumbnail set --url` asks Gumroad to download and attach a public HTTP(S) image directly.405406### files — Upload and recover file attachments407408```sh409# Upload a file and print the canonical Gumroad URL410gumroad files upload ./pack.zip --json --no-input411gumroad files upload ./pack.zip --name "Art Pack.zip" --json --no-input412413# Finalize a saved recovery manifest after a state-unknown upload414gumroad files complete --recovery recovery.json --yes --json --no-input415jq '.error.recovery' err.json | gumroad files complete --recovery - --yes --json --no-input416417# Abort an orphaned multipart upload418gumroad files abort --upload-id up-123 --key attachments/u/k/original/pack.zip --yes --json --no-input419```420421`files upload` and `files complete` both return `.file_url`. When a JSON upload fails with recovery details, reuse `.error.recovery` with `files complete` to finish it or `files abort` to reclaim the orphaned multipart upload.422423### media — Public media library (page images)424425Files from `files upload` are stored privately and can never be displayed on custom product landing pages or profile pages — the pages' Content-Security-Policy only allows Gumroad's public CDN, and page moderation cannot fetch private URLs, so a page embedding one fails review every time. To put an image on a page, upload it to the public media library instead and embed the returned `.media.url` in the page HTML before `pages push` / `products page publish`.426427```sh428# Host an image publicly and print its page-embeddable URL429gumroad media upload ./logo.png --json --no-input430gumroad media upload ./logo.png --name "Store logo" --json --jq '.media.url' --no-input431432# List and delete media library files433gumroad media list --json --no-input434gumroad media delete k3n8xq1p9wr2sd4a --yes --json --no-input435```436437The CLI detects JPEG, PNG, GIF, WebP, BMP, and ICO images up to 10 MB. It rejects SVG and other formats it cannot identify locally. Gumroad checks each image again and moderates it before hosting. A flagged image fails with the moderation message. Deleting a file breaks each page that still embeds its URL. The upload command requires the `edit_profile` scope. The list command requires the `view_profile` scope. Tokens created before the CLI requested these scopes fail with `Access denied: This endpoint requires the view_profile scope.` Run `gumroad auth login` again to create a token with the new scopes.438439If an upload returns `media_commit_state_unknown`, do not retry it automatically. Read `.error.recovery.key`. List the media and find the item whose URL contains that key. If the item exists, the upload completed. If it does not exist, keep `.error.recovery.signed_blob_id` and `.error.recovery.key` for support. If an upload returns `media_direct_upload_state_unknown`, do not start a new upload. Keep the same recovery values for support. If it returns `media_output_failed`, the upload completed. Use `.error.recovery.media_id` and `.error.recovery.media_url`. Do not retry the upload. If a delete returns `media_delete_output_failed`, the deletion completed. Do not retry it. If a delete returns `media_delete_state_unknown`, list the media and search for `.error.recovery.media_id`. Do not retry the deletion automatically.440441### emails — Manage audience emails442443```sh444# Create a draft from an HTML body file (or - for stdin). Draft is the default safety behavior.445gumroad emails create --subject "New release" --body ./email.html --json --no-input446gumroad emails create --subject "Product update" --body ./email.html --audience product --product <id> --json --no-input447448# Preview before sending; use `.preview_url` for human review.449gumroad emails send-preview <id> --json --jq '.preview_url' --no-input450gumroad emails view <id> --json --no-input451452# List drafts, scheduled emails, or sent emails.453gumroad emails list --state dra454455…(truncated)