01 · Overview
Base URL, auth, limits, errors
One base URL. One auth header. JSON in, JSON out.
https://api.gocushy.com/api/v1
Authentication
Every request carries your API key as a bearer token. Keys start with gc_ and come from your dashboard after signup. Keys are revocable per merchant.
curl https://api.gocushy.com/api/v1/me \ -H "Authorization: Bearer gc_your_key"
{
"account": {
"id": "01J2QJ3M8XN4V6T9R3E7M1B0CD",
"name": "Acme Courses",
"email": "sam@example.com",
"plan": "founding",
"stripe_connected": true
}
}Conventions
- Money is integer cents —
4900= $49.00. Currency is a lowercase 3-letter ISO code. - IDs are ULIDs — 26-character sortable strings.
- Timestamps are ISO 8601.
- Send
Content-Type: application/jsonandAccept: application/json.
Rate limit
120 requests per minute per API key, across the whole /api/v1 surface. Exceed it and you get a 429. Back off and retry.
Errors
Errors are JSON, always with a human-readable message.
| Status | Shape | When |
|---|---|---|
401 | {"error": "missing_api_key" | "invalid_api_key", "message": "..."} | No bearer token, wrong prefix, or a revoked key. |
404 | {"message": "..."} | Resource doesn't exist or isn't yours — scoping never leaks other accounts' data. |
422 | {"message": "...", "errors": {"field": ["..."]}} or {"error": "slug", "message": "..."} | Validation failure, or a domain rule (e.g. not_refundable, invalid_offer). |
429 | Throttle response | Over 120 req/min. |
02 · MCP integration
The recommended path: let an agent drive
gocushy is MCP-native. Connect the server and any MCP client — Claude Desktop, Claude Code, Cursor, ChatGPT, your own agents — gets 22 tools that run a complete sales operation: products, offers, checkout copy, email follow-up, webhooks, affiliates, refunds. The REST API below is the same surface; MCP is just the fastest way to use it.
Local (stdio) — Claude Desktop, Claude Code, Cursor
One command, one env var. The package is @gocushy/mcp on npm.
GOCUSHY_API_KEY=gc_your_key npx -y @gocushy/mcp
Config JSON for Claude Desktop or Cursor (claude_desktop_config.json / .cursor/mcp.json):
{
"mcpServers": {
"gocushy": {
"command": "npx",
"args": ["-y", "@gocushy/mcp"],
"env": { "GOCUSHY_API_KEY": "gc_your_key" }
}
}
}Remote (Streamable HTTP) — ChatGPT and hosted agents
No install. The remote server is stateless and key-scoped per request. Two ways to authenticate:
- Key in the path — for clients that can't send custom headers (e.g. ChatGPT developer mode):
https://gocushy-mcp.fly.dev/mcp/gc_your_key - Bearer header —
https://gocushy-mcp.fly.dev/mcpwithAuthorization: Bearer gc_your_key
All 22 tools
Every tool wraps a REST endpoint and returns JSON. The one thing an agent cannot do is complete Stripe identity verification — connect_payment returns a URL for the human, and that's the only human step.
| Tool | What it does | Key inputs |
|---|---|---|
connect_payment | Starts Stripe onboarding; returns the hosted URL the human must complete. | — |
payment_status | Checks whether onboarding finished and charges are enabled. | — |
create_product | Creates a sellable product. Prices in cents; subscriptions need an interval. | name, price_cents, currency, type (one_time | subscription), interval (month | year), delivery_url, delivery_note |
import_products | Imports active products from the connected Stripe account. Idempotent. | — |
list_products | Lists products with IDs and prices. | — |
create_offer | Composes a checkout: main product + optional order bump + one-click upsell. Returns a live checkout URL immediately. | name, product_id, bump_product_id + bump_headline, upsell_product_id + upsell_headline, upsell_body, success_url |
update_checkout | Sets the selling content on a checkout — headline, bullets, testimonials, guarantee, deadline, accent. Replaces existing blocks. | offer_id, checkout (object or null) |
get_checkout_link | Hosted checkout URL (and short cush.link) for an offer. | offer (name, slug, or ID) |
get_embed_code | Copy-paste snippet that overlays the checkout on any website. | offer |
list_orders | Recent orders: buyer, status, totals, bump/upsell taken. | limit (1–100, default 20) |
get_sales | Revenue, order counts, bump/upsell take rates, per-offer breakdown. | days (1–365, default 30) |
connect_email | Connects one of 13 providers: mailchimp, activecampaign, kit, getresponse, drip, mailerlite, klaviyo, brevo, beehiiv, loops, flodesk, hubspot, emailoctopus. Credentials validated live, stored encrypted; returns audiences. | provider, api_key, api_url (ActiveCampaign), default_list_id |
set_followup | Maps an offer to email actions: list + tags on a trigger. | offer_id, connection_id, trigger (purchase | refund | upsell_taken | renewal | abandoned), list_id, tags |
set_business_details | Sets the merchant profile: legal name, address, tax registration, invoice prefix, tax collection. | business_name, business_address, tax_number, tax_country, invoice_prefix, support_email, collect_tax |
create_webhook | Subscribes an endpoint to order events. Returns the HMAC signing secret exactly once. | url (https), events |
verify_purchase | Checks whether an email bought from this merchant — for gating content and support. | email, product_id, offer_id |
create_affiliate | Registers an affiliate; returns their referral code. Rates in basis points. | name, email, code, default_commission_bps, payout_note |
list_affiliates | All affiliates with referral codes and currently-due balances. | — |
get_affiliate_link | Shareable referral link for an affiliate + offer. Attribution is server-side. | affiliate_id, offer |
mark_commissions_paid | Records that the merchant sent an affiliate their released balance. Bookkeeping, not a money movement. | affiliate_id |
cancel_subscription | Cancels a buyer's subscription — at period end by default, or immediately. | order_id, immediately |
refund_order | Refunds a paid order, full or partial. Irreversible; agents are told to confirm with the merchant first. | order_id, amount_cents |
03 · REST reference
Every endpoint, grouped
All paths are relative to https://api.gocushy.com/api/v1. All require the bearer key.
Account & Stripe
/meAccount snapshot: id, name, email, plan, stripe_connected./stripe/connectStart (or resume) Stripe onboarding. Returns onboarding_url — the one step a human must complete./stripe/statusReturns stripe_account_id, charges_enabled, onboarded.Products
/productsNon-archived products, newest first./productsCreate a product. price_cents 50–99,999,999. type one_time (default) or subscription; subscriptions require interval month|year./import/stripe-productsImport the merchant's existing Stripe catalog. Idempotent — re-running updates instead of duplicating. Products without a usable default price, and recurring prices, are skipped and reported.curl -X POST https://api.gocushy.com/api/v1/products \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Launch Course",
"description": "The complete 6-module launch system.",
"price_cents": 4900,
"currency": "usd",
"delivery_url": "https://members.example.com/launch-course",
"delivery_note": "Log in with the email you purchased with."
}'{
"product": {
"id": "01J2QK5W8XN4V6T9R3E7M1B0AA",
"name": "Launch Course",
"description": "The complete 6-module launch system.",
"price_cents": 4900,
"currency": "usd",
"type": "one_time",
"interval": null,
"delivery_url": "https://members.example.com/launch-course",
"delivery_note": "Log in with the email you purchased with.",
"created_at": "2026-08-01T09:30:00.000000Z"
}
}Product parameters
| Field | Type | Rules |
|---|---|---|
name | string | required, ≤255 |
description | string | ≤5000 |
price_cents | integer | required, 50–99,999,999 |
currency | string | 3-letter ISO code, e.g. usd, nzd |
type | string | one_time (default) | subscription |
interval | string | month | year — required when type is subscription |
delivery_url | url | ≤2000 — becomes the "Access your purchase" button on receipts and the signed thank-you page (paid orders only) |
delivery_note | string | ≤500 — instructions shown with the access button |
Offers
/offersAll offers with products, checkout URLs, and blocks./offersCompose a checkout. Goes live immediately; slug auto-generated from the name./offers/{id}Update name, status (draft|live|archived), success_url, commission_bps, and checkout blocks.curl -X POST https://api.gocushy.com/api/v1/offers \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Launch Course",
"product_id": "01J2QK5W8XN4V6T9R3E7M1B0AA",
"bump_product_id": "01J2QK5W8XN4V6T9R3E7M1B0BB",
"bump_headline": "Add the workbook — printable worksheets for every module",
"upsell_product_id": "01J2QK5W8XN4V6T9R3E7M1B0CC",
"upsell_headline": "Upgrade: three live group coaching calls",
"commission_bps": 3000,
"checkout": {
"headline": "Launch your course in 30 days",
"subheadline": "The exact system, module by module — no theory.",
"bullets": [
"Six modules, one launch plan",
"Swipe files for every email",
"Lifetime updates included"
],
"testimonials": [
{
"quote": "Paid for itself in the first week.",
"name": "Mia T.",
"role": "Course creator"
}
],
"guarantee": {
"title": "30-day money-back guarantee",
"body": "Email us within 30 days for a full refund — no forms, no calls."
},
"deadline": "2026-08-15T23:59:00Z",
"button_text": "Get instant access",
"accent": "#1a8a60"
}
}'{
"offer": {
"id": "01J2QN7B2XN4V6T9R3E7M1B0DD",
"name": "Launch Course",
"slug": "launch-course",
"status": "live",
"checkout_url": "https://gocushy.com/c/01J2QJ3M8XN4V6T9R3E7M1B0CD/launch-course",
"cush_link": "https://cush.link/k3x9f2",
"product": { "id": "01J2QK5W8XN4V6T9R3E7M1B0AA", "name": "Launch Course", "price_cents": 4900 },
"bump_product": { "id": "01J2QK5W8XN4V6T9R3E7M1B0BB", "name": "Launch Workbook", "price_cents": 1700 },
"bump_headline": "Add the workbook — printable worksheets for every module",
"upsell_product": { "id": "01J2QK5W8XN4V6T9R3E7M1B0CC", "name": "Group Coaching", "price_cents": 9900 },
"upsell_headline": "Upgrade: three live group coaching calls",
"checkout_blocks": { "headline": "Launch your course in 30 days", "…": "…" }
}
}Offer parameters
| Field | Type | Rules |
|---|---|---|
name | string | required, ≤255 — the slug is generated from it |
product_id | id | required, must be your product |
bump_product_id | id | optional, must differ from the main product; requires bump_headline (≤255) |
upsell_product_id | id | optional, must differ from the main product; requires upsell_headline (≤255); upsell_body ≤5000 |
success_url | url | ≤2000 — where the buyer lands after everything (default: hosted thank-you page) |
commission_bps | integer | 0–9000 — per-offer affiliate rate in basis points (3000 = 30%) |
checkout | object | null | See Checkout blocks |
422 invalid_offer if you try.Orders & refunds
/orders?limit=50Recent orders, newest first. limit defaults to 50./orders/export?from&toAccounting-grade CSV. Optional from/to dates (YYYY-MM-DD, inclusive)./orders/{id}/refundRefund a paid order — full by default, partial via amount_cents./orders/{id}/cancel-subscriptionCancel at period end (default) or pass immediately: true.curl "https://api.gocushy.com/api/v1/orders?limit=2" \ -H "Authorization: Bearer gc_your_key"
{
"orders": [
{
"id": "01J2QP8XN4V6T9R3E7M1B0CDEE",
"kind": "purchase",
"status": "paid",
"buyer_email": "buyer@example.com",
"buyer_country": "NZ",
"currency": "usd",
"subtotal_cents": 6600,
"tax_cents": 990,
"total_cents": 7590,
"refunded_cents": 0,
"bump_taken": true,
"upsell_taken": false,
"invoice_number": "ACME-0042",
"offer": { "id": "01J2QN7B2XN4V6T9R3E7M1B0DD", "name": "Launch Course", "slug": "launch-course" },
"created_at": "2026-08-01T10:13:58.000000Z"
}
]
}Order status: pending, paid, failed, refunded, partially_refunded. Order kind: purchase or renewal (subscription renewals auto-generate orders, invoices, and receipts).
curl -X POST https://api.gocushy.com/api/v1/orders/01J2QP8XN4V6T9R3E7M1B0CDEE/refund \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{ "amount_cents": 1700 }'{
"order": {
"id": "01J2QP8XN4V6T9R3E7M1B0CDEE",
"status": "partially_refunded",
"refunded_cents": 1700,
"total_cents": 7590
}
}Refunds cascade automatically: the order.refunded webhook fires, refund follow-up rules run in your ESP, and affiliate commissions are clawed back. Omit amount_cents to refund the full remaining balance. Only paid or partially-refunded orders are refundable (422 not_refundable otherwise), and you can never refund more than remains (amount_cents is capped server-side).
Subscriptions
Subscriptions are products with type: "subscription" and an interval, sold through single-product offers, billed directly on the merchant's Stripe. Each renewal creates a new order with kind: "renewal" — it shows up in GET /orders, the CSV export, stats, webhooks, and Xero like any other paid order.
curl -X POST https://api.gocushy.com/api/v1/orders/01J2QP8XN4V6T9R3E7M1B0CDEE/cancel-subscription \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{}'
# → { "canceled": true, "when": "at period end" }
# Pass { "immediately": true } to cut access now.Orders without a subscription return 422 not_a_subscription.
Stats & export
/stats?days=30Rolling window, 1–365 days. Revenue, counts, take rates, per-offer breakdown.{
"window_days": 30,
"revenue_cents": 412300,
"orders_paid": 61,
"orders_failed": 4,
"orders_refunded": 2,
"bump_take_rate": 0.377,
"upsell_take_rate": 0.213,
"by_offer": [
{ "name": "Launch Course", "orders": 48, "revenue_cents": 322000 },
{ "name": "Workbook Solo", "orders": 13, "revenue_cents": 90300 }
]
}The CSV export (GET /orders/export) returns these columns: date, invoice_number, order_id, kind, status, offer, buyer_email, buyer_country, currency, subtotal_cents, tax_cents, total_cents, bump, upsell, reverse_charged. Values are formula-escaped, so the file is safe to open directly in Excel or Sheets.
Connections & follow-ups
/connectionsConnected email providers with status and last error./connectionsConnect an email provider: mailchimp, activecampaign, kit, getresponse, drip, mailerlite, klaviyo, brevo, beehiiv, loops, flodesk, hubspot, or emailoctopus. Credentials are validated live (your audiences are fetched) before anything is stored — encrypted. Only ActiveCampaign also needs api_url./followupsAll follow-up rules across offers./followupsMap an offer to an ESP action: trigger (purchase | refund | upsell_taken | renewal | abandoned), list_id, tags (≤20, each ≤100 chars).Business
/businessThe merchant business profile./businessUpdate business_name, business_address, tax_number, tax_country (ISO-2), invoice_prefix, support_email, collect_tax. Feeds the checkout footer, receipts, invoices, and Xero.collect_tax: true to charge real tax at checkout via Stripe Tax on the merchant's own registrations — including EU B2B reverse charge and NZ/AU GST. Requires Stripe Tax activated in the merchant's Stripe dashboard.Webhooks
/webhooksSubscribed endpoints with last_success_at and last_error./webhooksSubscribe an https endpoint. Returns the signing secret exactly once./webhooks/{id}Remove an endpoint.Full delivery and signature details in Webhooks & signatures below.
Verify purchase
/verify-purchase?email&product_id&offer_id"Did this email buy this?" — for gating access. Details in Verify purchase below.Affiliates & commissions
/affiliatesAll affiliates with codes and due balances (per currency)./affiliatesRegister an affiliate. Code auto-generated if omitted./affiliates/{id}/link?offer=Referral link for an offer (by ID or slug): the checkout URL + ?via=code. Attribution is server-side — no JavaScript fragility./affiliates/{id}/mark-paidSettle the released balance after you've actually paid them./commissions?affiliate_id&statusThe commission ledger. status: pending | due | paid | reversed. Returns up to 200 rows.curl -X POST https://api.gocushy.com/api/v1/affiliates \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Jess Parker",
"email": "jess@example.com",
"code": "jess",
"default_commission_bps": 3000,
"payout_note": "PayPal: jess@example.com"
}'{
"affiliate": {
"id": "01J2QR9C4XN4V6T9R3E7M1B0FF",
"name": "Jess Parker",
"email": "jess@example.com",
"code": "jess",
"default_commission_bps": 3000,
"payout_note": "PayPal: jess@example.com"
}
}How the ledger works: commissions accrue on referred sales at the rate locked at purchase (per-offer commission_bps wins over the affiliate default), hold for a 30-day refund window as pending, then become due. Refunds create clawbacks that net against future releases. Recurring commissions accrue on renewals. Self-referrals are blocked.
curl -X POST https://api.gocushy.com/api/v1/affiliates/01J2QR9C4XN4V6T9R3E7M1B0FF/mark-paid \ -H "Authorization: Bearer gc_your_key"
{
"marked_paid_cents": 14700,
"marked_paid_by_currency": { "usd": 14700 },
"affiliate": { "id": "01J2QR9C4XN4V6T9R3E7M1B0FF", "name": "Jess Parker", "code": "jess", "payout_note": "PayPal: jess@example.com" },
"note": "Record the actual transfer with your payout provider — gocushy tracks the ledger, your money does the paying."
}Mark-paid settles per currency, and only when the net balance is positive — clawback debt is never "paid away"; it stays due and nets against future commissions.
04 · Checkout blocks
Described, not designed
You (or your agent) describe the selling content; gocushy renders it into one battle-tested checkout layout. Every field is plain text, length-capped, and HTML-escaped into fixed zones — an agent can fully customise a checkout and structurally cannot break it. No templates, no custom CSS, no layout options. Set blocks via POST /offers, PUT /offers/{id}, or the update_checkout MCP tool.
| Field | Type | Limits & validation |
|---|---|---|
headline | string | ≤120 chars. Replaces the product name as the H1 (the product name moves to the order summary). Write the outcome, not the product name. |
subheadline | string | ≤200 chars. |
bullets | array of strings | ≤6 items, each ≤120 chars. Rendered as a checkmark list. Benefits, not features. |
testimonials | array of objects | ≤3 items: quote (required, ≤300), name (required, ≤80), role (optional, ≤80). Real testimonials only — fabrication violates the acceptable use policy. |
guarantee | object | title (required with guarantee, ≤80), body (optional, ≤240). Rendered as a badge box. |
deadline | ISO datetime | Must be in the future when set. Renders an honest countdown that hides once passed and can never reset — evergreen/fake timers are impossible by construction. |
image_url | url | https only, ≤2000 chars. Rendered as a plain image above the headline. |
button_text | string | ≤40 chars. Overrides the pay button label. |
accent | hex colour | Strict #rrggbb. Rejected if luminance (0.299R + 0.587G + 0.114B) / 255 exceeds 0.7 — button text is white and must stay readable. |
checkout object replaces the stored blocks entirely. Always send every field you want to keep. Send "checkout": null to reset to the plain checkout. Unknown keys are stripped server-side — only the nine fields above are stored.The untouchable core — payment form, order bump, totals, pay button — renders between your blocks and is computed server-side. An agent cannot ship a mispriced or broken checkout.
05 · Webhooks
Signed events, delivered with retries
Events
| Event | Fires when |
|---|---|
order.paid | An order is paid — including subscription renewals. |
order.refunded | An order is refunded, fully or partially. |
order.failed | A payment attempt fails. |
order.upsell_taken | The buyer accepts the one-click post-purchase upsell. |
Subscribe
curl -X POST https://api.gocushy.com/api/v1/webhooks \
-H "Authorization: Bearer gc_your_key" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/webhooks/gocushy", "events": ["order.paid", "order.refunded"] }'{
"webhook": {
"id": "01J2QS0D5XN4V6T9R3E7M1B0GG",
"url": "https://example.com/webhooks/gocushy",
"events": ["order.paid", "order.refunded"],
"active": true
},
"signing_secret": "cwh_kJ8mN2pQ4rS6tU8vW0xY2zA4bC6dE8fG1hJ3kL5m",
"note": "Store this secret now — it is not shown again. Verify: hex HMAC-SHA256 of the raw request body, header X-Cushy-Signature."
}The endpoint must be https. Omit events to subscribe to all four. The signing_secret (prefix cwh_) is returned exactly once — store it immediately.
Delivery
- Request:
POSTwith a JSON body and headersContent-Type: application/json,X-Cushy-Event(the event name), andX-Cushy-Signature. - Success: any 2xx response within 15 seconds.
- Retries: up to 5 attempts with backoff — 30s, 2 min, 10 min, then 60 min after the failed attempts.
- Health:
GET /webhooksshowslast_success_atand thelast_errorper endpoint.
{
"event": "order.paid",
"sent_at": "2026-08-01T10:14:07+00:00",
"order": {
"id": "01J2QP8XN4V6T9R3E7M1B0CDEE",
"status": "paid",
"buyer_email": "buyer@example.com",
"buyer_name": "Alex Buyer",
"total_cents": 7590,
"tax_cents": 990,
"currency": "usd",
"bump_taken": true,
"upsell_taken": false,
"offer": { "id": "01J2QN7B2XN4V6T9R3E7M1B0DD", "name": "Launch Course", "slug": "launch-course" },
"items": [
{ "kind": "main", "product_id": "01J2QK5W8XN4V6T9R3E7M1B0AA", "product_name": "Launch Course", "amount_cents": 4900 },
{ "kind": "bump", "product_id": "01J2QK5W8XN4V6T9R3E7M1B0BB", "product_name": "Launch Workbook", "amount_cents": 1700 }
],
"created_at": "2026-08-01T10:13:58+00:00"
}
}Item kind is main, bump, or upsell. Because retries exist, treat deliveries as at-least-once: key your processing on order.id + event to stay idempotent.
Verify the signature
X-Cushy-Signature is the lowercase hex HMAC-SHA256 of the raw request body, keyed with your signing secret. Compute it over the exact bytes you received — parse the JSON only after verifying.
const crypto = require("crypto");
const express = require("express");
const app = express();
app.post(
"/webhooks/gocushy",
express.raw({ type: "application/json" }), // raw body — do not use express.json() here
(req, res) => {
const signature = req.get("X-Cushy-Signature") || "";
const expected = crypto
.createHmac("sha256", process.env.CUSHY_WEBHOOK_SECRET)
.update(req.body) // Buffer of the raw bytes
.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.status(401).send("bad signature");
const { event, order } = JSON.parse(req.body);
if (event === "order.paid") {
// grant access for order.buyer_email
}
res.sendStatus(200);
}
);<?php
$secret = getenv('CUSHY_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CUSHY_SIGNATURE'] ?? '';
if (!hash_equals(hash_hmac('sha256', $raw, $secret), $signature)) {
http_response_code(401);
exit('bad signature');
}
$payload = json_decode($raw, true);
if ($payload['event'] === 'order.paid') {
// grant access for $payload['order']['buyer_email']
}
http_response_code(200);06 · Verify purchase
Gate anything on a real purchase
One GET answers "did this email buy this?" — use it to gate course access, community invites, downloads, or support. Paid and partially-refunded orders count; fully refunded ones don't.
| Param | Type | Notes |
|---|---|---|
email | string | required — matched case-insensitively |
product_id | id | optional — only orders containing this product (main, bump, or upsell) |
offer_id | id | optional — only orders through this offer |
curl "https://api.gocushy.com/api/v1/verify-purchase?email=buyer%40example.com&product_id=01J2QK5W8XN4V6T9R3E7M1B0AA" \ -H "Authorization: Bearer gc_your_key"
{
"purchased": true,
"orders": [
{
"id": "01J2QP8XN4V6T9R3E7M1B0CDEE",
"offer_id": "01J2QN7B2XN4V6T9R3E7M1B0DD",
"status": "paid",
"total_cents": 7590,
"currency": "usd",
"created_at": "2026-08-01T10:13:58.000000Z"
}
]
}Example: gate an Express route
async function requirePurchase(req, res, next) {
const email = req.user.email; // however you identify the visitor
const params = new URLSearchParams({
email,
product_id: process.env.COURSE_PRODUCT_ID,
});
const r = await fetch(
`https://api.gocushy.com/api/v1/verify-purchase?${params}`,
{ headers: { Authorization: `Bearer ${process.env.GOCUSHY_API_KEY}` } }
);
const { purchased } = await r.json();
if (!purchased) return res.status(403).send("No purchase found for this email.");
next();
}
app.get("/course", requirePurchase, (req, res) => res.render("course"));07 · Embed
Checkout overlay on any site
One attribute, one script tag. Works inside anything that renders HTML — Lovable output, WordPress, Webflow, hand-coded pages. No dependencies, ~3 KB.
<a href="https://gocushy.com/c/YOUR_ACCOUNT_ID/your-offer-slug" data-cushy>Buy now</a> <script src="https://api.gocushy.com/embed.js" defer></script>
Clicking any element with data-cushy opens the checkout in a centered overlay (iframe with allow="payment"). Escape or a click outside closes it. The href is used as the checkout URL; set data-cushy-url instead to keep a different href.
- Progressive: without JavaScript the link still works — it just navigates to the hosted checkout. The script upgrades it to an overlay.
- SPA-safe: a MutationObserver re-binds buttons injected after page load — builders and client-side routers included.
- Programmatic:
window.Cushy.open(url)andwindow.Cushy.close().
Your agent can fetch this snippet pre-filled for any offer with the get_embed_code MCP tool.
08 · Going live
Test vs live is just which Stripe you connect
There is no sandbox toggle in gocushy. The mode is decided by which Stripe account the merchant connects — connect a Stripe test-mode account to develop with test cards; connect the real account and the same offers, webhooks, and tools sell for real. The platform handles both identically.
Launch checklist
- Connect the real Stripe —
POST /stripe/connect, complete onboarding, confirm withGET /stripe/status(charges_enabled: true). - Set business details — name, address, tax registration, invoice prefix. This is what appears on checkouts, receipts, and compliant invoices.
- Wire your systems — a webhook for
order.paid, follow-up rules for your ESP,verify-purchasefor gating. - Point real traffic at the hosted checkout link, the embed, or affiliate links.
For agents
Machine-readable platform discovery lives at api.gocushy.com/llms.txt — what an agent can do here, how to connect, and the guardrails it can rely on: server-side pricing, at-most-once upsell charges, signed buyer-sensitive URLs, honest countdowns by construction.