Subscriptions
A subscription (sub_…) binds a customer to a
price and drives recurring billing from the customer’s
wallet.
Create a subscription
Section titled “Create a subscription”curl -s "$TRILE_API/v1/subscriptions" \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cus_01ARZ3NDEKTSV4RRFFQ69G5FAW", "priceId": "price_01ARZ3NDEKTSV4RRFFQ69G5FAY" }'import { Configuration, SubscriptionsApi } from '@trilehq/sdk'
const subscriptions = new SubscriptionsApi(new Configuration({ apiKey: process.env.TRILE_KEY }))
const { data: subscription } = await subscriptions.createSubscription({ createSubscription: { customerId: 'cus_01ARZ3NDEKTSV4RRFFQ69G5FAW', priceId: 'price_01ARZ3NDEKTSV4RRFFQ69G5FAY', }, idempotencyKey: crypto.randomUUID(),})// subscription.status → "active"What happens on create:
- No trial → the first cycle is charged immediately from the wallet. If the balance is
short, you get
400 insufficient_fundsand no subscription is created. - Trial price → status starts
trialing; the first charge is deferred to the trial’s end. - Subscribing a customer to a price they already hold returns
409 subscription_already_exists.
Subscription status
Section titled “Subscription status”| Status | Meaning |
|---|---|
trialing | In a free trial; not yet charged. |
active | Current cycle paid; billing normally. |
past_due | A renewal couldn’t be covered by the wallet. Recoverable — customer tops up, Trile retries. |
canceled | Ended. No further billing. |
Each subscription carries currentPeriodStart / currentPeriodEnd so you know when the next
charge is due.
Retrieve & list
Section titled “Retrieve & list”# Retrievecurl "$TRILE_API/v1/subscriptions/sub_01ARZ3..." -H "x-api-key: $TRILE_KEY"
# List, filtered by status (cursor paginated)curl "$TRILE_API/v1/subscriptions?status=active&limit=50" -H "x-api-key: $TRILE_KEY"// Retrieveconst { data: sub } = await subscriptions.getSubscription({ id: 'sub_01ARZ3...' })
// List, filtered by status (cursor paginated)const { data: page } = await subscriptions.listSubscriptions({ status: 'active', limit: '50' })// page.items, page.nextCursorUpdate
Section titled “Update”curl -X PATCH "$TRILE_API/v1/subscriptions/sub_01ARZ3..." \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "cancelAtPeriodEnd": true, "metadata": { "plan_note": "downgrade pending" } }'const { data: updated } = await subscriptions.updateSubscription({ id: 'sub_01ARZ3...', updateSubscription: { cancelAtPeriodEnd: true, metadata: { plan_note: 'downgrade pending' }, }, idempotencyKey: crypto.randomUUID(),})Use cancelAtPeriodEnd to schedule a cancellation without cutting off access immediately.
Cancel
Section titled “Cancel”# Cancel at end of current period (customer keeps access until then)curl -X POST "$TRILE_API/v1/subscriptions/sub_01ARZ3.../cancel" \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "cancelAtPeriodEnd": true }'
# Cancel immediatelycurl -X POST "$TRILE_API/v1/subscriptions/sub_01ARZ3.../cancel" \ -H "x-api-key: $TRILE_KEY" -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "cancelAtPeriodEnd": false }'// Cancel at end of current period (customer keeps access until then)await subscriptions.cancelSubscription({ id: 'sub_01ARZ3...', cancelSubscription: { cancelAtPeriodEnd: true }, idempotencyKey: crypto.randomUUID(),})
// Cancel immediatelyawait subscriptions.cancelSubscription({ id: 'sub_01ARZ3...', cancelSubscription: { cancelAtPeriodEnd: false }, idempotencyKey: crypto.randomUUID(),})React to billing
Section titled “React to billing”Don’t poll for renewals. Subscribe to webhooks:
subscription.created/subscription.updated/subscription.canceledinvoice.paid— a cycle was charged successfullyinvoice.payment_failed— wallet couldn’t cover the cycle; subscription is nowpast_due
See The wallet model for how past_due recovers when the customer
tops up.