Skip to main content
The Pocketsflow HTTP API lets you manage products, checkout, orders, payments, subscribers, customers, discounts, upsells, refunds, webhooks, newsletters, your Link in Bio page, images, and the partner program — all from your own systems.

Open the interactive API reference

Browse every endpoint, see request/response schemas, and try calls live at api.pocketsflow.com/docs.

Browse every public endpoint

See all public paths, methods, parameters, request bodies, and response codes generated from the OpenAPI contract.
The raw OpenAPI specification is served at https://api.pocketsflow.com/docs.json — import it into Postman/Insomnia or generate a typed client from it.

Base URL

Every request is made over HTTPS to:
There is no version prefix in the path — endpoints are addressed directly (for example GET /orders). See Versioning for how the API changes.

Authentication

Every endpoint requires authentication via the Authorization header using the Bearer scheme, except the few marked public below (they are used by the hosted checkout). Two credential types are accepted: Create an API key in the dashboard under Developers → API keys, then send it on every request:
All data is automatically scoped to the account that owns the key — you never pass a user id. The key decides the mode: a pk_test_… key always reads and writes test-mode data and a pk_live_… key always live data, whatever the test-mode toggle in the dashboard says: lists and searches return that mode’s data and anything you create lands in it. GET /users/me reports the key’s mode as testMode. Reads and updates by id act on your own items in either mode, so keep test and live ids apart in your integration. Keys are stored hashed, can be given an expiry, and can be archived/revoked at any time; a revoked or expired key returns 401. Accounts in a country the API does not serve receive 403 with "code": "COUNTRY_RESTRICTED".
Treat secret API keys like passwords. Never expose them in client-side code, public repositories, or URLs. Prefer a dedicated, revocable key per integration, and rotate a key immediately if it leaks.

Authentication errors

Missing or malformed credentials return 401 with a JSON body:
Common cases: no Authorization header, a header that doesn’t start with Bearer , an empty token, and an inactive/archived/expired key.

Making requests

  • Send JSON bodies with Content-Type: application/json. POST /images takes multipart/form-data, and the product and Link in Bio write endpoints also accept multipart/form-data when you upload files.
  • Responses are JSON. IDs are Mongo ObjectId strings (24 hex characters) unless noted (payment-processor ids are plan_…, pay_…, mem_…).
  • Timestamps are ISO 8601 UTC strings (2026-01-24T05:35:38.103Z).
  • Money in API requests and responses is a decimal amount in major currency units (e.g. 12.1 = $12.10) — product prices, orders, payments, subscribers, discounts, upsells. Only fields whose name ends in Cents are integer cents. Webhook payloads have their own rules — see Webhook events.

Pagination

List endpoints paginate in one of the following ways. Keep requesting page + 1 while hasMore is true. The page-based pagination object looks like:

Errors

Errors use standard HTTP status codes and a JSON body. Most endpoints return a message:
Authentication failures return both error and message, and some errors add a machine-readable code (for example COUNTRY_RESTRICTED, ACCOUNT_BLOCKED, ACCOUNT_DELETED, SEND_ALLOWANCE_EXCEEDED):
Read message first, fall back to error, and branch on code when it is present. A few older endpoints answer validation errors with a plain-text body.

Rate limits

Requests authenticated with a valid API key get 600 requests per minute per account. Unauthenticated traffic (public endpoints, or a request whose key is rejected) shares 200 requests per minute per IP address, so always send your key. Responses carry RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (seconds until the window resets) and RateLimit-Policy headers. Exceeding a limit returns 429 with a Retry-After header (seconds) and a JSON body:
Back off and retry after that delay. Beyond that, use reasonable concurrency, back off on 5xx, and cache where you can. Some endpoints have their own caps — for example POST /newsletters/send accepts at most 100 recipients per request.

Versioning

The API is unversioned — there is no version prefix in the path. Changes are additive: new endpoints, optional request fields, response fields, and webhook event types can appear at any time, so ignore fields you don’t recognise and never depend on field order. Breaking changes are announced in the changelog before they ship.

Endpoint catalog

Everything below is available with an API key (a few endpoints are public and say so). Full request/response schemas live in the interactive reference and the endpoint reference; the most important response shapes are shown here.

Products

POST /products accepts (among others) name, price, compareAtPrice, description, subtitle, published (new products are always created published; send false on update to unpublish), slug, thumbnail, images[], hasProductPage ("yes" or "no"), payWant, minPrice, maxPrice, showSales, showReviews, refundPolicy, hasFirstName, hasLastName, and productType.
  • Prices are decimal amounts in your account currency (currency on GET /users/me), not cents. compareAtPrice is the crossed-out “was” price — display only, it never changes what the buyer pays.
  • refundPolicy is the id of one of your refund policies (from POST /refunds), not the policy text.
  • Pay-what-you-want products need a product page: a request that sets payWant: true must also send hasProductPage: "yes", otherwise it fails with 400.
  • Subscription (recurring) offers are a separate resource: fetch them with GET /subscriptions/{id} and delete them with DELETE /subscriptions/{id}. Updating a subscription offer is not available with an API key yet.
POST /products/copy/{id} duplicates one of your catalog items. Send { "testMode": false } to copy a test-mode offer into live mode (“Copy to live”); omit the body to copy within the same mode. The copy gets a deduplicated Name (Copy) name and starts unpublished when copied into live mode, so nothing can be charged before you review it. A copied one-time product fires the product.created webhook against the copy’s own mode.

Checkout

Required: productId (a product or subscription offer id). Optional: successUrl and cancelUrl (absolute http(s) URLs), customerEmail, lockEmail, clientReferenceId (max 256 characters), discountCode, and metadata (an object, echoed back on the resulting webhooks). Invalid values return 400; a productId that is not yours returns 404. Your account needs a store subdomain first — the checkout link lives on it.

Orders

Payments

The unified payment ledger — one-time product purchases and subscription charges (initial + every renewal) in a single resource. Every payment is a Sale. A single Payment object:
boolean
true for subscription payments (initial + renewals); false for one-time product purchases.
string
Present only on subscription payments: initial for the first charge that activates the membership, renewal for every recurring charge after it.
string
Set on one-time purchases; empty on subscription payments (use subscriptionId instead).
string
The payments partner’s receipt, plan, and membership ids backing the sale. membershipId is present on subscription payments only and matches the subscriber’s membershipId.
Some responses also carry older duplicate keys for these ids and for the paymentsPartner, paymentsPartnerLive and membership blocks below, with the same values. They are deprecated and will only be removed after notice — read the names documented here.
GET /payments returns { payments: [Payment], pagination }. GET /payments/{id}?live=true returns a PaymentDetail:
net = amountBeforeTax − affiliateCut − processingFee; fee is the processor fee. paymentsPartnerLive is null if the live lookup failed — the stored paymentsPartner block still applies, so ?live=true never fails the request.
The total transaction cost on a sale is itemized — currently an estimated 4.7% + $0.30 (about $5.00 on $100), covering the Pocketsflow platform, payment infrastructure, tax handling when required, and dispute prevention. The order detail is authoritative: it uses the settled payment and returns the platform fee, processing fee, total transaction cost, effective fee rate, and net amount. See Pricing & fees.

Subscriptions and subscribers

Two distinct concepts: a subscription offer is the recurring product you sell; a subscriber is a buyer’s membership in one of those offers.
Each subscriber carries a portalUrl — the hosted self-service portal where the buyer manages, cancels, or resumes their subscription. The same link is delivered as portalUrl in the customer.subscription.created / .updated webhooks. Send it to that buyer exactly as returned: its ?token=… is what allows cancel/resume — a portal link built by hand from the two ids opens read-only.
Subscription offer creation and /subscriptions/{id} accept both an API key and an Auth0 JWT. In SDK v1.2.0 and later, Node.js and TypeScript users can call pocketsflow.subscriptionOffers.create(). See the SDK guide for installation and usage; use the REST request above if your installed SDK predates v1.2.0.
A Subscriber object carries a processor-sourced live status (incomplete, incomplete_expired, trialing, active, past_due, canceled, unpaid, succeeded, refunded, paused) and the joined offer:
GET /subscriptions/subscribers/{id}?live=true:

Discounts

valueType is percentage (default, value at most 100) or fixed (a decimal amount off in your account currency); value must be greater than 0. Optional active and expiration (an ISO 8601 date after which checkout rejects the code; null removes it). mainProductIds must all be your own products or subscription offers in the key’s mode, otherwise 400.

Upsells

Optional: name, offer, compareAtPrice, upsellDescription, primaryButtonText, secondaryButtonText, active, and the colour/font overrides. upsellProductId must be one of your products and mainProductIds your own products in the key’s mode. On update, upsellProductId, upsellPrice, and mainProductIds change only when sent; the other fields are written as sent. GET /upsells returns { upsells, salesWithUpsells }.

Refunds

These endpoints manage refund policies — the refund terms shown to buyers at checkout, which products reference by id (refundPolicy). They do not refund payments: refunding a payment is done from the dashboard and has no API-key endpoint. Refunds that happen arrive as the order.refunded webhook and as isRefunded: true on the payment.

Reviews

Webhooks

Deliveries are signed (X-Pocketsflow-Signature-V2: t=<unix>,v1=<hex HMAC-SHA256> plus the legacy X-Pocketsflow-Signature), carry the event id in X-Pocketsflow-Event-Id and the body’s id, and are retried up to 3 attempts on 5xx, 429, and connections that never reached your endpoint. See Webhook events for the full event list, payloads, and headers, and Authentication & security for signature verification.

Newsletters

A post has a name (internal, required), subject, preview (inbox preview text), contentHtml (the email body; contentJson is the optional editor document), and an audience: sendToAll, subscriberIds, productIds (everyone who bought those products), and/or tags. The sender (fromName, replyToEmail) defaults to your newsletter settings, which must be configured first. status is draft (default), scheduled, sending, sent, failed, or automation; you can set draft, scheduled (with scheduledAt and optional scheduledTimezone), or automation. Any status other than draft/automation needs subject, preview, fromName, and contentHtml.
Then send it with POST /newsletters/posts/{id}/send, which answers { "message": "Post queued for sending", "post": { … } }. Sending requires a verified sender email (400 otherwise); exceeding your monthly send allowance returns 402 (code: "SEND_ALLOWANCE_EXCEEDED"), and a restricted account or an over-limit subscriber count returns 403. Page fields: creatorName, creatorBio, username (3–30 letters, numbers, - or _; it is also your store subdomain), socialLinks and regularLinks (arrays of { name, url, customName? }, at most 20 each — the whole array is replaced on update), profilePicture, coverImage, images, template, themeV2, gaMeasurementId, selectedProductIds / selectedSubscriptionIds (which of your products the page shows), subscriberForm, bookingEnabled, showFirstName, and showLastName. There are no per-link endpoints: to add, edit, or remove a link, send the full regularLinks array with PUT /creator-pages.

Images

Customers

Account

Use subdomain to build product/checkout URLs (https://{subdomain}.pocketsflow.com/...). testMode reflects the key you called with (true for a pk_test_… key). canSell is false while selling is paused on the account. Other fields may appear in the response; they are dashboard-internal and can change without notice. A suspended account answers 403 (code: "ACCOUNT_BLOCKED") and a deleted one 410 (code: "ACCOUNT_DELETED").

Partners

Connect an AI agent

Prefer to drive Pocketsflow from an AI assistant? The MCP server exposes this entire API as Model Context Protocol tools — add https://api.pocketsflow.com/mcp to Claude, ChatGPT, Cursor, or your own agent, sign in, and click Allow access, and it can manage your account in natural language. Connecting over MCP needs no API key; the REST API and SDK keep using the API keys described above.

Building an integration?

If you’re connecting Pocketsflow to an external store or platform (for example WooCommerce), start with the Integrations section — it walks through the end-to-end pattern with code examples.