Subscriptions

Subscriptions

Subscriptions let you charge customers on a recurring schedule. You define a price with a billing interval, create a subscription linking a customer and a payment method to that price, and the billing engine handles invoice generation and payment collection automatically.

Before creating a subscription, you need:

Subscription lifecycle

  1. Create a recurring price that defines the amount, currency, and billing interval
  2. Create a subscription with at least one item referencing a recurring price
  3. An invoice is generated and the saved payment method is charged automatically
  4. On success, the subscription becomes ACTIVE and renews at the end of each billing period
  5. On payment failure, the subscription enters PAST_DUE and eventually UNPAID if not resolved

Step 1: Create a recurring price

API reference

A price defines the amount, currency, and billing schedule. Set the recurring field to make it a recurring price that can be attached to subscriptions.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/prices \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "price": {
      "display_name": "Pro Plan (Monthly)",
      "currency": "GBP",
      "unit_amount": "2900",
      "recurring": {
        "interval": "MONTH",
        "interval_count": 1
      }
    }
  }'

Response

{
  "name": "accounts/acct_YOUR_ACCOUNT/prices/pr_PRICE_ID",
  "display_name": "Pro Plan (Monthly)",
  "currency": "GBP",
  "unit_amount": "2900",
  "active": true,
  "recurring": {
    "interval": "MONTH",
    "interval_count": 1
  },
  "create_time": "2026-02-19T08:30:12Z",
  "update_time": "2026-02-19T08:30:12Z"
}

The unit_amount is in the smallest currency unit — 2900 = 29.00 GBP.

Prices are designed to be stable. Instead of changing the amount or interval on an existing price, create a new price and deactivate the old one by setting active to false via UpdatePrice.

Deactivating a price prevents it from being used for new subscriptions, but existing subscriptions that already reference it will continue to bill against it.

Recurring intervals

IntervalExample
DAYEvery N days
WEEKEvery N weeks
MONTHEvery N months
YEAREvery N years

Set interval_count to bill at multiples — for example, interval: MONTH with interval_count: 3 means "every 3 months".

MONTH and YEAR intervals use calendar arithmetic anchored to the subscription's start date. For example, a monthly subscription created on January 31 will next bill on February 28 (or 29 in a leap year), then March 31, and so on.

Step 2: Create a subscription

API reference

Create a subscription with at least one item referencing a recurring price. All items must share the same billing interval and currency.

The Idempotency-Key header lets you safely retry the request if it fails — see Idempotency.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID"
    },
    "items": [
      {
        "display_name": "Pro Plan",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PRICE_ID",
        "quantity": 1
      }
    ]
  }'

Response

{
  "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID",
  "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
  "state": "ACTIVE",
  "billing_cycle_anchor": "2026-02-19T08:31:10Z",
  "items": [
    "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_ID"
  ],
  "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID",
  "latest_invoice": "accounts/acct_YOUR_ACCOUNT/invoices/in_INVOICE_ID",
  "next_action_time": "2026-03-19T08:31:10Z",
  "create_time": "2026-02-19T08:31:10Z",
  "update_time": "2026-02-19T08:31:10Z"
}

On creation, the billing engine immediately generates an invoice and attempts to charge the saved payment method. If payment succeeds, the subscription transitions to ACTIVE. If payment fails, the subscription stays in INCOMPLETE and the invoice remains OPEN. You can complete activation by paying the invoice directly via the pay invoice endpoint — for example, with a different payment method. If the invoice is not paid within 23 hours, the subscription expires to INCOMPLETE_EXPIRED.

The billing_cycle_anchor is the reference timestamp for computing future billing periods. The next_action_time shows when the next scheduled action will occur.

Multiple items

A subscription can have multiple items. Each item references a recurring price and has its own quantity. All items must share the same billing interval and currency.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID"
    },
    "items": [
      {
        "display_name": "Pro Plan",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PLAN_PRICE_ID",
        "quantity": 1
      },
      {
        "display_name": "Extra seats",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_SEAT_PRICE_ID",
        "quantity": 5
      }
    ]
  }'

Each item generates its own line on the subscription's invoices. The total charged is the sum of unit_amount * quantity across all items.

Free trials

To start a subscription with a free trial, set trial_end to a future timestamp. The subscription starts in TRIALING — the payment method is not charged at creation.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID",
      "trial_end": "2026-03-05T00:00:00Z"
    },
    "items": [
      {
        "display_name": "Pro Plan",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PRICE_ID"
      }
    ]
  }'
{
  "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID",
  "state": "TRIALING",
  "trial_end": "2026-03-05T00:00:00Z",
  "next_action_time": "2026-03-05T00:00:00Z"
}

At trial_end, the billing engine automatically generates an invoice and charges the payment method. On success the subscription transitions to ACTIVE. On failure it moves to PAST_DUE.

How billing works

The billing engine runs automatically. At each billing event (activation or renewal), it:

  1. Creates an invoice with line items for each subscription item
  2. Finalizes the invoice
  3. Charges the subscription's default_payment_method

Invoices generated by subscriptions follow the same lifecycle as regular invoices — you can view them via the invoice endpoints. The subscription's latest_invoice field always points to the most recently generated invoice.

Payment failures and grace periods

When a renewal payment fails, the subscription moves to PAST_DUE. If the outstanding invoice is paid within the grace period — either by the customer through the hosted invoice URL or by your server via the pay invoice endpoint — the subscription automatically transitions back to ACTIVE.

If the grace period expires without payment, the subscription transitions to UNPAID. Once UNPAID, paying the outstanding invoice directly will not reactivate the subscription — you must use the ReactivateSubscription endpoint instead (see Reactivating an unpaid subscription).

You can configure grace_period_days to control how long the subscription stays in PAST_DUE before transitioning to UNPAID.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID",
      "grace_period_days": 7
    },
    "items": [
      {
        "display_name": "Pro Plan",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PRICE_ID"
      }
    ]
  }'

The grace period must be shorter than the billing interval — for example, less than 28 days for a monthly subscription.

Applying tax rates

Set default_tax_rates on the subscription to apply tax rates to all invoice lines. If a specific subscription item has its own tax_rates, those take precedence over the subscription-level defaults.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_PAYMENT_METHOD_ID",
      "default_tax_rates": [
        "accounts/acct_YOUR_ACCOUNT/tax-rates/txr_DEFAULT_TAX_RATE_ID"
      ]
    },
    "items": [
      {
        "display_name": "Pro Plan",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PLAN_PRICE_ID"
      },
      {
        "display_name": "Extra seats",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_SEAT_PRICE_ID",
        "quantity": 5,
        "tax_rates": [
          "accounts/acct_YOUR_ACCOUNT/tax-rates/txr_ITEM_TAX_RATE_ID"
        ]
      }
    ]
  }'

In this example, the "Pro Plan" item inherits txr_DEFAULT_TAX_RATE_ID from the subscription, while the "Extra seats" item uses its own txr_ITEM_TAX_RATE_ID instead.

Managing subscription items

You can add, update, and remove items on an active subscription. Changes take effect on the next billing cycle.

Newly added items will not have a current_period until the next invoice is generated and paid. An item without a current_period has been added mid-cycle and has not yet been billed.

Adding an item

API reference

The new item's price must have the same billing interval and currency as existing items.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "item": {
      "display_name": "Add-on storage",
      "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_ADDON_PRICE_ID",
      "quantity": 2
    }
  }'

Updating an item

API reference

You can update an item's display_name, price, quantity, tax_rates, and metadata. Use the update_mask field to specify which fields to change.

When updating the price on a single item, the new price must have the same billing interval and currency as other items on the subscription. If the subscription has only one item, this constraint does not apply — you can change to any recurring price.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_ID \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription_item": {
      "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_ID",
      "quantity": 10
    },
    "update_mask": "quantity"
  }'

Changing billing frequency

All items on a subscription must share the same billing interval and currency. This means you cannot change a single item's price to a different interval when other items still use the old one.

To change the billing frequency (for example, moving a customer from monthly to yearly), use the bulk update endpoint to update all items at once. When every item is included in the update, the interval and currency constraints are validated against the new prices rather than the existing ones.

API reference

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items:bulk-update \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "updates": [
      {
        "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_1",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_NEW_PRICE_ID_1"
      },
      {
        "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_2",
        "price": "accounts/acct_YOUR_ACCOUNT/prices/pr_NEW_PRICE_ID_2"
      }
    ]
  }'

Removing an item

API reference

A subscription must always have at least one item. The last remaining item cannot be deleted.

curl -X DELETE https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID/subscription-items/si_ITEM_ID \
  -H "Authorization: Api-Key $API_KEY"

Canceling a subscription

API reference

Canceling an ACTIVE subscription schedules cancellation at the end of the current billing period. The subscription moves to CANCELLATION_REQUESTED immediately, and the customer retains access until the period ends, at which point it transitions to CANCELED.

For PAST_DUE, UNPAID, or TRIALING subscriptions, cancellation takes effect immediately.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID:cancel \
  -H "Authorization: Api-Key $API_KEY"

Response

{
  "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID",
  "state": "CANCELLATION_REQUESTED",
  "cancel_time": "2026-03-19T08:31:10Z",
  "next_action_time": "2026-03-19T08:31:10Z"
}

The cancel_time indicates when the subscription will transition to CANCELED.

Reactivating an unpaid subscription

API reference

If a subscription reaches UNPAID due to payment failures, you can reactivate it. This immediately attempts a new payment. You can optionally provide a new payment method.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID:reactivate \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_NEW_PAYMENT_METHOD_ID"
  }'

If payment succeeds, the subscription returns to ACTIVE.

Updating a subscription

API reference

You can update the default_payment_method, default_tax_rates, grace_period_days, and metadata on a subscription. Updates are only allowed when the subscription is in TRIALING, ACTIVE, PAST_DUE, or UNPAID.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "name": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID",
      "default_payment_method": "accounts/acct_YOUR_ACCOUNT/payment-methods/pm_NEW_PAYMENT_METHOD_ID"
    },
    "update_mask": "default_payment_method"
  }'

Listing subscriptions

API reference

You can filter subscriptions by state, customer, and create_time range.

curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/subscriptions:list \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "state": "ACTIVE",
      "customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID"
    }
  }'

Subscription states

StateDescription
INCOMPLETECreated but not yet activated — payment is being attempted. Expires after 23 hours
INCOMPLETE_EXPIREDActivation was not completed in time (terminal state)
TRIALINGIn a free trial period — converts automatically at trial_end
ACTIVEHealthy and billing on schedule
PAST_DUEA renewal payment failed — paying the invoice within the grace period restores to ACTIVE
UNPAIDGrace period expired without payment — must use ReactivateSubscription to recover
CANCELLATION_REQUESTEDCancellation scheduled — remains active until cancel_time
CANCELEDSubscription has ended — no further billing (terminal state)

Key subscription fields

FieldDescription
customerResource name of the customer (set at creation, cannot be changed)
default_payment_methodPayment method used for automatic charges
trial_endEnd of the free trial period (set at creation, cannot be changed)
billing_cycle_anchorReference timestamp for computing billing periods
itemsResource names of the subscription's items
latest_invoiceMost recently generated invoice
next_action_timeWhen the next renewal or scheduled action occurs
cancel_timeWhen a cancellation-requested subscription will be canceled
grace_period_daysDays to wait after a payment failure before transitioning to UNPAID
metadataKey-value pairs for your own use (max 50 keys, key max 40 chars, value max 500 chars)

Key subscription item fields

FieldDescription
display_nameLabel shown on invoices
priceResource name of the recurring price this item bills against
quantityUnits billed per period (defaults to 1)
tax_ratesItem-level tax rates — overrides the subscription's default_tax_rates when set
current_periodThe current billing period window (start and end timestamps)
metadataKey-value pairs for your own use (max 50 keys, key max 40 chars, value max 500 chars)

Webhooks

Subscription state changes generate events that are sent to your webhook endpoints:

Event typeDescription
subscription.activeSubscription became active (initial activation or recovery from past due)
subscription.past_dueA payment failed and the subscription entered the grace period
subscription.unpaidGrace period expired without successful payment
subscription.canceledSubscription was canceled
subscription.incomplete_expiredIncomplete subscription expired without activation

See Webhooks for setup instructions.