Subscriptions
A subscription bills a customer on a fixed cadence using a previously-saved payment token. Genius Checkout owns the schedule — we charge on every renewal, retry declines, and emit webhooks. You don't need a cron job.
A subscription always needs two things up front: a customer_id (returned from any prior checkout) and a token_id (pt_…). Both are merchant-scoped.
Lifecycle
trial ─▶ active ─▶ past_due ─▶ cancelled
└▶ suspended ─▶ active
└▶ cancelled
└▶ expired (total_cycles reached)| Status | Meaning |
|---|---|
trial | Created with trial_period_days. No charge yet — next charge fires when the trial ends. |
active | Billing on schedule. Every cycle emits subscription.charged on success. |
past_due | The most recent renewal failed. The dunning job retries on the merchant's schedule. |
suspended | Paused by the merchant via POST /subscriptions/{id}/pause. No charges fire until resumed. |
cancelled | Terminal. No further charges. Set via POST /subscriptions/{id}/cancel. |
expired | Terminal. Reached total_cycles (fixed-term subscriptions). |
Create a subscription
POST /api/v1/subscriptions| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | External customer id (cus_…) |
token_id | string | Yes | External payment-token id (pt_…). Must be active. |
amount | integer | Yes | Per-cycle amount in minor units (see Currencies) |
currency | string | Yes | ISO 4217 alpha code |
interval | string | Yes | day, week, month, year |
interval_count | integer | No | Multiplier on interval. Default 1. Range 1–365. |
trial_period_days | integer | No | If set, the first charge fires after the trial. |
setup_fee | integer | No | One-off fee charged immediately on creation. |
total_cycles | integer | No | Fixed-term subscription. Status flips to expired after the last cycle. |
start_date | date | No | ISO 8601 date in the future. Defaults to today. |
metadata | object | No | Echoed on every subscription.* webhook. |
curl -X POST https://app.geniuscheckout.com/api/v1/subscriptions \
-H "Authorization: Bearer gc_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"customer_id": "cus_abc123",
"token_id": "pt_xyz789",
"amount": 2999,
"currency": "USD",
"interval": "month",
"trial_period_days": 14,
"metadata": {"plan": "pro"}
}'Response (201)
{
"id": "sub_abc123",
"status": "trial",
"amount": 2999,
"currency": "USD",
"interval": "month",
"interval_count": 1,
"setup_fee": null,
"trial_ends_at": "2026-06-05T12:00:00+00:00",
"current_period_start": "2026-05-22T12:00:00+00:00",
"current_period_end": "2026-06-22T12:00:00+00:00",
"next_charge_at": "2026-06-05T12:00:00+00:00",
"total_cycles": null,
"completed_cycles": 0,
"dunning_attempts": 0,
"cancelled_at": null,
"cancel_reason": null,
"customer": {"id": "cus_abc123", "name": "Jane Doe", "email": "[email protected]"},
"payment_method": {"id": "pt_xyz789", "brand": "Visa", "last4": "0071"},
"created_at": "2026-05-22T12:00:00+00:00"
}List subscriptions
GET /api/v1/subscriptions?status=active&limit=20&offset=0Filters: status, customer_id. Pagination is limit/offset (max limit=100). See Pagination.
{
"data": [ /* subscription objects */ ],
"total": 142,
"limit": 20,
"offset": 0
}Retrieve a subscription
GET /api/v1/subscriptions/{id}Returns the full subscription object plus an events array of recent cycle events (status_changed, charged, charge_failed).
Cancel
POST /api/v1/subscriptions/{id}/cancel{ "reason": "Customer request", "immediate": true }When immediate is false, the cancellation is scheduled at current_period_end (the customer keeps access until then). Default is true.
Pause / resume
POST /api/v1/subscriptions/{id}/pause
POST /api/v1/subscriptions/{id}/resumePausing flips the status to suspended and stops all future charges. Resuming returns it to active and recalculates next_charge_at.
Update payment method
PUT /api/v1/subscriptions/{id}/payment-method{ "token_id": "pt_new_card" }The new token must be active and belong to the same merchant. Use this when a card expires and the customer adds a new one via your portal.
Webhooks
Every cycle and state transition fires a webhook — see Webhooks for the full event list. The most common ones for subscriptions:
subscription.created— created (intrialoractive)subscription.charged— successful renewalsubscription.charge_failed— declined renewal; dunning will retrysubscription.cancelled— terminal cancelsubscription.updated— payment method or schedule changed
Code samples
// Node — create a subscription with a 14-day trial
const res = await fetch('https://app.geniuscheckout.com/api/v1/subscriptions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GC_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
customer_id: 'cus_abc123',
token_id: 'pt_xyz789',
amount: 2999,
currency: 'USD',
interval: 'month',
trial_period_days: 14,
}),
})
const subscription = await res.json()// PHP (Guzzle) — cancel at period end
$client = new \GuzzleHttp\Client();
$client->post("https://app.geniuscheckout.com/api/v1/subscriptions/{$id}/cancel", [
'headers' => [
'Authorization' => 'Bearer ' . getenv('GC_API_KEY'),
'Content-Type' => 'application/json',
],
'json' => [
'reason' => 'Customer request',
'immediate' => false,
],
]);Next
- Payment Tokens — list / deactivate saved cards
- Webhooks — listen for renewal events
- Tokenization & Recurring — first-time card capture
