REST API
Base address: https://regular-subs.fly.dev/api/v1. Send your key as Authorization: Bearer <key>. Responses are JSON and carry an x-request-id header.
The machine-readable version is /api/v1/openapi.json (OpenAPI 3.1).
Changes have their own endpoints, listed with the reads for the same job. The body is the change's input and path parameters fill in the rest. Add ?preview=true to get a plan instead of applying, and X-Change-Reason to say why (agents must). Every change takes an Idempotency-Key header for safe retries.
Start here
List the changes a key can make
GET /api/v1/commands
Every change this key can make, with its input schema, risk and whether it can be undone.
Permission: any key · Returns: { commands: [{ name, description, risk, reversible, input, http }] }
See how subscriptions are doing
GET /api/v1/overview
How the store's subscriptions are doing: whether everything is working, active subscribers, monthly recurring revenue, charges due this week, and setup progress. The best first call.
Permission: subscriptions:read · Returns: { checks, metrics, trend, forecast, topProducts, activity, setup }
Subscriptions
Find subscriptions
GET /api/v1/subscriptions
Find subscriptions, newest first. Filters combine.
Permission: subscriptions:read · Returns: { subscriptions: Subscription[], total, nextCursor }
| Parameter | In | Type | Description |
|---|---|---|---|
status | query | string | Comma-separated: ACTIVE, PAUSED, CANCELLED, EXPIRED, FAILED. |
customerId | query | string | A Shopify customer GID. |
productId | query | string | Has a line for this Shopify product GID. |
variantId | query | string | Has a line for this Shopify variant GID. |
sellingPlanId | query | string | Has a line on this selling plan. |
nextBillingFrom | query | string | Next charge on or after this time (ISO 8601). |
nextBillingTo | query | string | Next charge on or before this time (ISO 8601). |
query | query | string | Product title, SKU, or the end of a contract or customer id. |
limit | query | integer | Results per page, 1–100. |
cursor | query | string | nextCursor from the previous page. |
Look up a subscription
GET /api/v1/subscriptions/:id
One subscription: its status, lines, prices, schedule, next charge and last payment.
Permission: subscriptions:read · Returns: { subscription: Subscription }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Our id, or the Shopify contract GID (URL-encoded). |
See a subscription's history
GET /api/v1/subscriptions/:id/history
What happened to one subscription, newest first, with who did each thing: the customer, staff, an agent, an API key or Shopify.
Permission: subscriptions:read · Returns: { subscriptionId, events: [{ at, type, by, to, reason, ... }] }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Our id, or the Shopify contract GID. |
Get a link for the customer to manage it
GET /api/v1/subscriptions/:id/portal-link
A link that opens this subscription in the customer portal on the store's site, or goes straight to cancelling it. Safe to send: the customer logs in first and only ever sees their own subscriptions.
Permission: subscriptions:read · Returns: { subscriptionId, view, url, note }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Our id, or the Shopify contract GID. |
view | query | string | manage (default) or cancel. |
Re-check subscriptions against Shopify
POST /api/v1/subscriptions/reconcile
Re-check every subscription against Shopify and fix anything out of date. Runs in the background; check progress with get_sync_status. Safe to run any time: it only reads from Shopify and updates our copy.
Permission: subscriptions:sync · Risk: low · Undo: no · Input
Skip the next delivery
POST /api/v1/subscriptions/:subscriptionId/skip
Skip the next delivery. The next charge moves on by one delivery interval (e.g. a month). Good for customers with too much product who'd otherwise cancel.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Move the next delivery
POST /api/v1/subscriptions/:subscriptionId/reschedule
Move the next delivery to a specific date (tomorrow to a year out). Later deliveries follow on from the new date at the usual interval.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Pause a subscription
POST /api/v1/subscriptions/:subscriptionId/pause
Pause a subscription: no charges or deliveries until it's resumed.
Permission: subscriptions:write · Risk: medium · Undo: yes · Input
Resume a subscription
POST /api/v1/subscriptions/:subscriptionId/resume
Resume a paused subscription. If its next delivery date has passed, the next delivery is tomorrow at the usual time.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Cancel a subscription
POST /api/v1/subscriptions/:subscriptionId/cancel
Cancel a subscription. No more charges or deliveries. Record why: it drives which save offers are worth making. Customers can always cancel in one step.
Permission: subscriptions:write · Risk: high · Undo: no · Input
Change a quantity
POST /api/v1/subscriptions/:subscriptionId/quantity
Change how many of an item come in each delivery. The price per item stays the same; the charge changes with the quantity.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Change how often it delivers
POST /api/v1/subscriptions/:subscriptionId/frequency
Change how often deliveries come, to another frequency on the same offer (e.g. every 4 weeks instead of every 2). The next charge date stays; later ones follow the new frequency.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Swap to another variant
POST /api/v1/subscriptions/:subscriptionId/variant
Switch an item to another variant of the same product (size, flavour, grind). The subscription discount carries over to the new variant's price.
Permission: subscriptions:write · Risk: low · Undo: yes · Input
Change the shipping address
POST /api/v1/subscriptions/:subscriptionId/address
Ship future deliveries to a new address. The shipping price and method stay as they are, and Shopify refuses an address the store doesn't ship to. The address isn't kept in the activity log.
Permission: subscriptions:write · Risk: low · Undo: no · Input
Cancellation offers
Get the offer for a customer who wants to cancel
GET /api/v1/subscriptions/:id/save-offer
The one offer for a customer who wants to cancel, for their reason (skip, deliver later, less often, pause, swap, or a discount the store allows). Null when there's nothing worth offering. Share it once; if they say no, cancel.
Permission: subscriptions:read · Returns: { offer: { kind, heading, detail, accept, ... } | null }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Our id, or the Shopify contract GID. |
reason | query | string | too_much, too_expensive, taking_break, not_needed, didnt_like or other. |
See how cancellation offers are doing
GET /api/v1/retention
How cancellation offers are doing: customers shown an offer, how many stayed, the save rate and monthly revenue kept, by reason and by offer.
Permission: subscriptions:read · Returns: { days, shown, saved, cancelled, saveRate, revenueKept, byOffer, byReason }
| Parameter | In | Type | Description |
|---|---|---|---|
days | query | integer | How far back to look. Default 30. |
Keep a customer with an offer
POST /api/v1/subscriptions/:subscriptionId/save
Take the offer shown to a customer who was about to cancel (skip, deliver later, less often, pause, swap, or a discount) instead of cancelling. Get the offer first with get_save_offer for the customer's reason; only that offer can be accepted. Recorded as a save in the retention report.
Permission: subscriptions:write · Risk: medium · Undo: no · Input
Offers
List offers
GET /api/v1/offers
The subscription offers shoppers can choose on product pages: name, frequencies, discount and products.
Permission: offers:read · Returns: { offers: Offer[] }
Look up an offer
GET /api/v1/offers/:id
One offer.
Permission: offers:read · Returns: { offer: Offer }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Our id, or the Shopify selling plan group GID. |
Create an offer
POST /api/v1/offers
Offer subscriptions on products: a name, one or more delivery frequencies, and an optional discount. Shoppers see it on the product page and choose a frequency at checkout.
Permission: offers:write · Risk: medium · Undo: yes · Input
Change an offer
PATCH /api/v1/offers/:id
Change an offer's name, description, delivery frequencies, discount or products. Existing subscriptions keep the terms they started with; changes apply to new subscriptions.
Permission: offers:write · Risk: medium · Undo: yes · Input
Stop offering a subscription
DELETE /api/v1/offers/:id
Stop offering a subscription option. It disappears from product pages. Existing subscriptions keep running on the terms they started with.
Permission: offers:write · Risk: high · Undo: no · Input
Billing
See how billing is going
GET /api/v1/billing/summary
Charges, failed payments, retries and what's due next.
Permission: billing:read · Returns: { due, retrying, failed, recent, ... }
List charges and payment attempts
GET /api/v1/billing/cycles
Billing cycles (one per renewal) with their payment attempts, newest first.
Permission: billing:read · Returns: { cycles: Cycle[], nextCursor }
| Parameter | In | Type | Description |
|---|---|---|---|
state | query | string | Comma-separated: scheduled, charging, retry_scheduled, succeeded, exhausted, skipped, cancelled. |
subscriptionId | query | string | Only this subscription's cycles. |
limit | query | integer | Results per page, 1–100. |
cursor | query | string | nextCursor from the previous page. |
Email a card update link
POST /api/v1/subscriptions/:subscriptionId/payment-update
Email the customer a secure Shopify link to update the card on this subscription. Use it after a declined or expired card, or when a customer asks. At most once an hour per subscription.
Permission: subscriptions:write · Risk: low · Undo: no · Input
Retry a failed payment now
POST /api/v1/billing/cycles/:cycleId/retry
Retry a failed charge now instead of waiting for the next scheduled retry, e.g. after the customer says they've updated their card. Charges the customer's payment method.
Permission: billing:write · Risk: financial · Undo: no · Input
Customer emails
See which customer emails were sent
GET /api/v1/notifications
Customer emails sent, skipped or failed, newest first, with why. Recipients are masked.
Permission: subscriptions:read · Returns: { notifications: [...], problems: { missingEmailAccess } }
| Parameter | In | Type | Description |
|---|---|---|---|
subscriptionId | query | string | Only this subscription's emails. |
limit | query | integer | Results per page, 1–100. |
Imports
List imports
GET /api/v1/imports
Subscriptions brought in from another app: each upload or fetch, its status and counts.
Permission: imports:read · Returns: { imports: Import[] }
Check an import
GET /api/v1/imports/:id
One import: problems grouped by kind with examples, warnings, a preview of what will be created, failures, and how many are waiting to bill.
Permission: imports:read · Returns: { import: ImportDetail }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | The import's id. |
Upload subscriptions to import
POST /api/v1/imports
Upload a subscriptions export from another app (Recharge, Skio, Appstle, Loop, Seal, Shopify Subscriptions or a spreadsheet). Nothing is created yet: each subscription is checked against the store (customer, saved card, products, date) in the background. Read the import to see what's ready and what needs fixing, then run imports.start.
Permission: imports:write · Risk: low · Undo: no · Input
Create the imported subscriptions
POST /api/v1/imports/:importId/start
Create the import's ready subscriptions in Shopify, keeping each one's next charge date, price and schedule. They're created in the background, one at a time. They don't bill until imports.release_billing, so the old app has time to be switched off. Subscriptions with problems are left out.
Permission: imports:write · Risk: medium · Undo: no · Input
Start billing imported subscriptions
POST /api/v1/imports/:importId/release-billing
Start billing an import's subscriptions here. Run it only once the old app has stopped charging them (turned off or uninstalled), or customers will be charged twice. Any whose charge date passed while waiting move to their next date, since the old app charged them.
Permission: imports:write · Risk: high · Undo: no · Input
Throw away an import
DELETE /api/v1/imports/:importId
Throw away an import that hasn't created anything yet, e.g. to upload a corrected file. Imports that have created subscriptions can't be discarded.
Permission: imports:write · Risk: low · Undo: no · Input
Connect Recharge
POST /api/v1/imports/connections
Connect the app the store is moving from with an API token from it, so subscriptions (and cards held at Stripe, Braintree or Authorize.net) can be fetched directly, and cancelled there when billing switches over. For Recharge the token needs: read_subscriptions, read_customers, read_payment_methods, write_subscriptions. The token is stored encrypted and never shown again.
Permission: imports:write · Risk: medium · Undo: no · Input
Disconnect Recharge
DELETE /api/v1/imports/connections/:source
Remove the stored API token for the app the store moved from. Delete the token in that app too.
Permission: imports:write · Risk: low · Undo: no · Input
Fetch subscriptions from Recharge
POST /api/v1/imports/connections/:source/fetch
Fetch every active subscription from the connected app (Recharge) and check them against the store, like an upload but always current. Nothing is created yet. Read the import, then run imports.start.
Permission: imports:write · Risk: low · Undo: no · Input
Plans and activity
Preview a change
POST /api/v1/plans
Preview any command without applying it. Returns exactly what would change and whether it needs a person's approval.
Permission: any key · Returns: { status: "planned", plan: Plan }
Takes an Idempotency-Key header for safe retries.
Check a preview
GET /api/v1/plans/:id
A plan: its preview, status (pending, awaiting_approval, applied, rejected, stale, expired) and what to do next.
Permission: any key · Returns: { plan: Plan }
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | The plan's id. |
Apply a preview
POST /api/v1/plans/:id/apply
Apply a plan exactly as previewed. Refused if anything changed since it was made.
Permission: any key · Returns: { status: "applied", auditId, diff, result } or { status: "planned" } while it waits for approval
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | The plan's id. |
Takes an Idempotency-Key header for safe retries.
See every change
GET /api/v1/activity
Every change ever applied, newest first: what, who, why, and whether it can be undone.
Permission: audit:read · Returns: { activity: [...], nextCursor }
| Parameter | In | Type | Description |
|---|---|---|---|
limit | query | integer | Results per page, 1–100. |
before | query | string | nextCursor from the previous page. |
Undo a change
POST /api/v1/activity/:id/undo
Reverse a change by running its recorded inverse as a new change. Agents get a plan to apply.
Permission: any key · Returns: Same as any change: applied, or a plan
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | The activity entry's id. |
Takes an Idempotency-Key header for safe retries.
Store
Read store settings
GET /api/v1/settings
Store settings: sender name, support email, retry schedule, customer emails, cancellation offers.
Permission: settings:read · Returns: { settings: Settings }
Check the last re-sync with Shopify
GET /api/v1/sync-runs
Recent re-checks of our data against Shopify: when, how many were checked, and how many were out of date.
Permission: subscriptions:read · Returns: { runs: [...] }
Change store settings
PATCH /api/v1/settings
Change store-wide subscription settings: sender name, support email, the failed-payment retry schedule, what happens after the final retry, and which customer emails are sent. Only the fields you pass change.
Permission: settings:write · Risk: medium · Undo: yes · Input
Connect Klaviyo
POST /api/v1/integrations/klaviyo
Send subscription events (started, cancelled, upcoming charge, order placed, payment failed...) to the store's Klaviyo account, for its own flows. The private API key needs: Accounts: Read, Events: Full, Profiles: Full.
Permission: integrations:write · Risk: medium · Undo: no · Input
Disconnect Klaviyo
DELETE /api/v1/integrations/klaviyo
Stop sending subscription events to Klaviyo and remove the stored key.
Permission: integrations:write · Risk: low · Undo: no · Input