gumroad CLI

SkillFiles & storage

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 policy", "check my refund policy", "check my earnings", "see my revenue", "who subscribed", "manage my store", "discount code", "coupon", "shipping status", "payout schedule", or any request to query or act on Gumroad data, even if the user doesn't say "Gumroad" explicitly but is clearly referring to their creator store or digital product sales. Do NOT trigger for Gumroad web UI, Rails, or codebase questions.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the gumroad CLI skill

What this skill tells your AI

The instructions your AI receives, as published by antiwork/gumroad-cli in skills/gumroad/SKILL.md and read by ahel’s review.

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, except marketing commands: those require the caller to supply yes: true after showing the exact text, account and link and obtaining seller confirmation. 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/download-style commands and non-page pull commands are marked read-only; pages_pull carries destructiveHint because it writes a local HTML file. Every other tool (including licenses_verify, which increments uses unless no-increment is true, pages_push and emails_send) also 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[]
  • marketing recommend → .channels[] (live entries include .handle, .action.id, .action.idempotency_key, .action.confirmation_token, .action.post_text, .action.link_url); marketing approve/schedule/cancel/status → .marketing_action, .handle; schedule also includes .intent_url, .connect_path. Inspect .marketing_action.status and .error_code: HTTP success is not proof of posting.
  • 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 refund-taxes → .message, .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.

Marketing launch posts

Requires the seller's auto_marketing flag and an edit_emails token (or the API's legacy account scope). A flag-off or foreign resource returns 404. The commands reuse the same action and tagged link as the product editor's Share tab; no local marketing state is created.

gumroad marketing recommend <product-id-or-permalink> --json --no-input
gumroad marketing status <action-id> --json --no-input
# Show exact post_text, handle and link_url to the seller; obtain confirmation before --yes.
gumroad marketing approve <action-id> --yes --confirmation-token <reviewed-token> --json --no-input
# schedule means execute NOW in this version, not at a future date. Approve first.
gumroad marketing schedule <action-id> --yes --confirmation-token <reviewed-token> --json --no-input
gumroad marketing cancel <action-id> --yes --json --no-input

Each mutation fetches and prints the exact action preview to stderr before confirmation; quoted text escapes control characters. --dry-run still performs that GET but sends no POST. The CLI returns the server's opaque idempotency and confirmation keys unchanged. Copy or account changes during confirmation are refused; review again rather than silently retrying altered content. Repeating approve/schedule resolves the same action, not another post. Do not request a new recommendation to retry a completed or uncertain post.

Human and plain action output append the server's connect_path and intent_url after the error code for reconnect and manual sharing. These fields use the same control-character escaping as the other columns; JSON and MCP retain the original response fields.

MCP exposes marketing_recommend, marketing_approve, marketing_schedule, marketing_cancel, and marketing_status. Unlike other mutations, marketing does not receive an implicit --yes: first show the preview and get approval, then supply {"args":["<action-id>"],"yes":true,"confirmation-token":"<reviewed-token>"}. The token must come from the preview the seller confirmed, not a fresh lookup; missing or changed tokens prevent approve/schedule. The server's X executor handles reconnect and unknown-result states; clients must surface error_code rather than claim success from HTTP 200.

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

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
56
Forks
35
Last commit
Sep 2026
Advanced
Item type
skill
Key
gumroad-antiwork
Source
github.com/antiwork/gumroad-cli