Skip to content

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)
StatusMeaning
trialCreated with trial_period_days. No charge yet — next charge fires when the trial ends.
activeBilling on schedule. Every cycle emits subscription.charged on success.
past_dueThe most recent renewal failed. The dunning job retries on the merchant's schedule.
suspendedPaused by the merchant via POST /subscriptions/{id}/pause. No charges fire until resumed.
cancelledTerminal. No further charges. Set via POST /subscriptions/{id}/cancel.
expiredTerminal. Reached total_cycles (fixed-term subscriptions).

Create a subscription

http
POST /api/v1/subscriptions
FieldTypeRequiredDescription
customer_idstringYesExternal customer id (cus_…)
token_idstringYesExternal payment-token id (pt_…). Must be active.
amountintegerYesPer-cycle amount in minor units (see Currencies)
currencystringYesISO 4217 alpha code
intervalstringYesday, week, month, year
interval_countintegerNoMultiplier on interval. Default 1. Range 1–365.
trial_period_daysintegerNoIf set, the first charge fires after the trial.
setup_feeintegerNoOne-off fee charged immediately on creation.
total_cyclesintegerNoFixed-term subscription. Status flips to expired after the last cycle.
start_datedateNoISO 8601 date in the future. Defaults to today.
metadataobjectNoEchoed on every subscription.* webhook.
bash
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)

json
{
  "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

http
GET /api/v1/subscriptions?status=active&limit=20&offset=0

Filters: status, customer_id. Pagination is limit/offset (max limit=100). See Pagination.

json
{
  "data": [ /* subscription objects */ ],
  "total": 142,
  "limit": 20,
  "offset": 0
}

Retrieve a subscription

http
GET /api/v1/subscriptions/{id}

Returns the full subscription object plus an events array of recent cycle events (status_changed, charged, charge_failed).

Cancel

http
POST /api/v1/subscriptions/{id}/cancel
json
{ "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

http
POST /api/v1/subscriptions/{id}/pause
POST /api/v1/subscriptions/{id}/resume

Pausing flips the status to suspended and stops all future charges. Resuming returns it to active and recalculates next_charge_at.

Update payment method

http
PUT /api/v1/subscriptions/{id}/payment-method
json
{ "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 (in trial or active)
  • subscription.charged — successful renewal
  • subscription.charge_failed — declined renewal; dunning will retry
  • subscription.cancelled — terminal cancel
  • subscription.updated — payment method or schedule changed

Code samples

js
// 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
// 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

Released under the proprietary Genius Checkout license.