Subscriptions API

Manage recurring billing with subscriptions and plans from your server.

Every endpoint needs a server keyand is scoped to the key's merchant. Subscriptions are also scoped to the key's mode: a sk_test_ key only sees test subscriptions. Responses use the standard envelope ({ success, errorCode, errorMessage, data }); the examples below show data. Subscriptions are started through a checkout session with paymentType: "subscription" and a subscriptionPlanId.

Retrieve a Subscription

GET/v1/subscriptions/{id}

Scope: subscription.read

{
  "id": 812,
  "object": "subscription",
  "status": "active",
  "clientId": 4410,
  "clientEmail": "[email protected]",
  "planId": 37,
  "planName": "Pro Monthly",
  "amount": 29.99,
  "currency": "USD",
  "interval": "month",
  "intervalCount": 1,
  "startDate": "2025-01-01T00:00:00+00:00",
  "currentPeriodStart": "2025-01-01T00:00:00+00:00",
  "currentPeriodEnd": "2025-02-01T00:00:00+00:00",
  "trialEnd": null,
  "cancelAtPeriodEnd": false,
  "canceledAt": null,
  "pausedAt": null,
  "hasPaymentMethod": true,
  "livemode": true,
  "items": [
    { "id": 901, "planId": 37, "planName": "Pro Monthly", "quantity": 1, "amount": 29.99, "currency": "USD" }
  ]
}

Statuses: active, trialing, past_due, unpaid, paused, canceled, pending.

List Subscriptions

GET/v1/subscriptions

Scope: subscription.read. Newest first.

ParameterTypeDescription
clientIdnumberFilter by customer
statusstringOne of the statuses above
planIdnumberSubscriptions with an item on this plan
limitnumber1–100, default 25
startingAfternumberCursor: subscriptions older than this id
{
  "object": "list",
  "data": [ { "id": 812, "object": "subscription", "status": "active", "...": "..." } ],
  "hasMore": false
}

Cancel a Subscription

POST/v1/subscriptions/{id}/cancel

Scope: subscription.update

{
  "cancelAtPeriodEnd": true,
  "reason": "Customer requested cancellation"
}

With cancelAtPeriodEnd: true (the default) the subscription stays active until the current period ends; false cancels it now. Returns the updated subscription. Cancelling a canceled subscription is a no-op.

Pause a Subscription

POST/v1/subscriptions/{id}/pause

Scope: subscription.update

{
  "reason": "Customer travelling"
}

Returns the subscription with status: "paused". Pausing a paused or canceled subscription is a 400. There is no scheduled resume date; call resume when billing should restart.

Resume a Subscription

POST/v1/subscriptions/{id}/resume

Scope: subscription.update

Reactivates a paused subscription; anything else is a 400. Returns the subscription.

Update Payment Method — not available

POST/v1/subscriptions/{id}/update-payment-method
Returns 501 not_implemented. A card on file needs the customer's consent, which your server cannot give for them, and the customer-facing card update is not available yet.

List a Subscription's Invoices

GET/v1/subscriptions/{id}/invoices

Scope: subscription.read. Newest first, up to 100.

{
  "object": "list",
  "data": [
    {
      "id": 5531,
      "object": "invoice",
      "invoiceNumber": "INV-000123",
      "status": "paid",
      "amount": 29.99,
      "amountPaid": 29.99,
      "currency": "USD",
      "periodStart": "2025-01-01T00:00:00+00:00",
      "periodEnd": "2025-02-01T00:00:00+00:00",
      "dueDate": "2025-01-01T00:00:00+00:00"
    }
  ],
  "hasMore": false
}

Plans

Plans define the price and billing interval. Every plan belongs to a subscription product you created in the merchant portal. Plans are shared by test and live keys.

Create a Plan

POST/v1/plans

Scope: subscriptionplan.create

{
  "productId": 12,
  "name": "Pro Monthly",
  "description": "Everything in Basic, plus priority support",
  "amount": 29.99,
  "currency": "USD",
  "interval": "month",
  "intervalCount": 1,
  "active": true
}

interval is day, week, month or year. active: false creates a draft. Trials are set per checkout session (trialDays), not on the plan. Returns the plan:

{
  "id": 37,
  "object": "plan",
  "productId": 12,
  "name": "Pro Monthly",
  "description": "Everything in Basic, plus priority support",
  "amount": 29.99,
  "currency": "USD",
  "interval": "month",
  "intervalCount": 1,
  "status": "available",
  "active": true,
  "createdAt": "2025-01-15T10:00:00+00:00"
}

List Plans

GET/v1/plans

Scope: subscriptionplan.read. Discontinued plans are left out unless includeArchived=true.

{
  "object": "list",
  "data": [ { "id": 37, "object": "plan", "name": "Pro Monthly", "...": "..." } ],
  "hasMore": false
}

Retrieve a Plan

GET/v1/plans/{id}

Scope: subscriptionplan.read. Returns the plan object.

Update a Plan

PATCH/v1/plans/{id}

Scope: subscriptionplan.update

{
  "name": "Pro Monthly (2025)",
  "description": "Now with API access",
  "active": true
}
amount, currency, interval and intervalCount cannot be changed; sending them is a 400. Create a new plan and move subscribers instead.

Archive a Plan

DELETE/v1/plans/{id}

Scope: subscriptionplan.delete. Discontinues the plan: existing subscriptions keep billing, new ones cannot start. Returns the plan with status: "discontinued".

Renewals and the Card on File

When a customer starts a subscription through checkout and pays its first invoice by card, we keep that card for the subscription's renewals. The checkout tells the customer this before they pay. The card is attached to the subscription, and to the customer if they have no card yet (hasPaymentMethod: true), and renewals are charged automatically as merchant-initiated payments. A subscription that starts on a free trial has no first payment, so no card is kept at checkout: point the customer to the portal to add one before the trial ends.

Customers

A subscription checkout (paymentType: "subscription") and a portal session need the clientId of one of your customers. Customers have no test/live mode: both keys see the same customers, while their subscriptions and invoices belong to one mode.

Create a Customer

POST/v1/customers

Scope: merchantclient.create

{ "email": "[email protected]", "name": "Andre Clarke", "phone": "+18765550100", "externalId": "tenant-9", "metadata": { "tenantId": "t-9" } }

Creating is safe to repeat: it returns the customer you already have. Without externalId, that is the one with the same email. With externalId (your own id for the customer, at most 100 characters, e.g. a tenant id), it is the one with that id, never matched by email, so two of your accounts that share a billing email stay separate customers with their own subscriptions and portal.

{ "id": 512, "object": "customer", "email": "[email protected]", "name": "Andre Clarke", "phone": "+18765550100", "externalId": "tenant-9", "metadata": { "tenantId": "t-9" }, "hasPaymentMethod": false, "createdAt": "2026-10-10T14:00:00+00:00" }

Find or List Customers

GET/v1/[email protected]&limit=1

Scope: merchantclient.read. ?externalId= finds a customer by your id. Newest first (limit 1–100, default 10), as { "object": "list", "data": [ …customers ], "hasMore": false }. GET /v1/customers/{id} returns one.

Products

Every plan belongs to a subscription product (a plan's productId).

POST/v1/products

Scopes: subscriptionproduct.create; subscriptionproduct.read for GET /v1/products (a list) and GET /v1/products/{id}.

{ "name": "Platform plans", "description": "What businesses pay for the platform" }