Subscriptions link a customer to a subscription plan, or to a subscription offer for items bought in a mixed cart, and automate recurring billing. Create one directly when you already have a Flint customer_id and a usable payment method for that customer; for Flint-hosted signup, sell the plan through a checkout session or payment link with subscription_plan_id instead. A subscription can start immediately, at a future time, or by importing an already-paid current period without charging.
A subscription is always in exactly one of six states:
status on subscriptionNon-trial immediate subscriptions start as incomplete while Flint collects the first paid period, then move to active; grant access only once the subscription is active. From there, the lifecycle covers trialing, paused, past_due, and canceled. Pausing preserves remaining prepaid time, cancellation can take effect immediately or at period end, and failed renewals enter dunning, where Flint retries the payment before applying the owner-specific end action. A customer can hold multiple independent subscriptions to the same plan.
A paused subscription reports pause_reason. delivery_action_required means a renewal was held because delivery couldn't be quoted, and then either the hold ran out or someone (the buyer or you) paused during the hold. A pause Flint starts when the hold runs out has no end date and waits for a resume. A pause with a length resumes on its own when the length is up, and starts a new hold if delivery still can't be quoted. Resuming by hand needs a delivery preference Flint can quote (409 SUBSCRIPTION_DELIVERY_UNAVAILABLE otherwise, and the subscription stays paused), starts a new billing period, and charges and ships right away.
billing_schedule_owner is flint when Flint computes renewal dates and external when your integration supplies them. An external subscription with no next_billing_at keeps its lifecycle status and reports awaiting_billing_schedule: true; use the billing-schedule and skip-cycle endpoints to manage its timer. Past-due subscriptions can create idempotent, pollable manual payment retries. Buyers can also start retries through POST /v1/me/subscriptions/{subscription_id}/payment-retries; their limit is 3 retries per billing period, counting retries started by the store too, while merchant requests keep their existing retry behavior.
See the Subscription billing guide for the full lifecycle and dunning behavior. Use webhooks like subscription.activated and subscription.past_due to drive entitlement changes.
When you send your own subscription email, link the buyer to the subscription with POST /v1/subscriptions/{subscription_id}/access-links. The url it returns opens the subscription in your Flint-hosted customer account without a sign-in for 14 days or 5 opens; pausing or canceling still needs the buyer to sign in. The url is a bearer credential: Flint returns it only in that response and in a retry with the same Idempotency-Key. The route needs commerce.subscriptions.read, and refuses a merchant_hosted customer account. See Link the buyer to their order.
curl -X POST https://api.withflintpay.com/v1/subscriptions/sub_01J5Z8N3QK4W7Y2RB6TPVXHC9D/access-links \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: subscription-link-sub_01J5Z8N3QK4W7Y2RB6TPVXHC9D"
Delivery#
A subscription to a plan with physical lines carries delivery: where each shipment goes and how. It is required on create for such a plan and rejected for a plan without physical lines.
| Field | Description |
|---|---|
type | shipment or local_delivery. |
delivery_method_id | The method each renewal is quoted with. |
destination | address, copied when delivery is written, and customer_address_id when it came from a saved address, for display only. Editing the saved address doesn't change this copy. Unlike the request, the response has no type. |
recipient | name, phone, and email of the person receiving the shipment. |
revision | Goes up by one on every change. |
address_verification.state | verified, needs_review, unverified, or unverifiable, from verification when delivery was written. Renewals don't verify again. |
delivery_method | The stored method for display: name, description, type, and price_type (fixed or quoted). |
shipping_money | Shipping for one shipment: the method's price when fixed, or the shipping charged on the latest shipment when quoted. Absent until a quoted method has charged once. |
updated_at | When delivery last changed. |
Flint clears delivery when the subscription becomes canceled. Each renewal order keeps its own delivery_destination. open_renewal_order_id names a renewal order that is paid but hasn't shipped. It keeps the destination it was paid with, even after delivery changes or the subscription is canceled. It is absent when there is no such order, and only renewal orders count, never the signup order.
Replace delivery as a whole with PATCH /v1/subscriptions/{subscription_id} and the subscription's current version as expected_version. Send destination with "type": "customer_address" and a customer_address_id, or "type": "address" and an address. Flint previews delivery before saving and rejects a method the plan doesn't offer or one that can't serve the address. The change applies from the next unpaid renewal, and the buyer is emailed. Use POST /v1/subscription-previews with mode: "delivery_options" to see which methods serve an address before saving.
Delivery holds#
When a renewal can't be quoted for delivery, Flint holds it instead of charging and reports delivery_hold:
| Field | Description |
|---|---|
reason | method_unavailable (another offered method serves the address), destination_not_served (no offered method does), or rate_unavailable (the method can't be priced right now). |
fixable_by | buyer for method_unavailable when the store lets buyers change delivery (can_update_delivery), merchant otherwise. |
started_at | When the hold started. |
ends_at | When the subscription pauses if the hold isn't resolved: 14 days or one billing interval after the billing date, whichever is sooner. |
delivery_hold is absent when nothing is held. While it is present, a payment retry fails with 409 SUBSCRIPTION_PAYMENT_RETRY_NOT_ALLOWED, from you or the buyer, and the buyer's retry_payment action is unavailable with not_in_state. See When a renewal can't ship.
upcoming_delivery_hold reports a renewal that will be held: a delivery check before the billing date failed. It has the same reason and fixable_by, plus detected_at and renewal_at, the billing date that will be held. It clears when a later check passes, the cycle is skipped, the subscription is paused or canceled, or the billing date moves, and at the billing date it becomes delivery_hold if delivery still can't be quoted. It is never present together with delivery_hold. Setting or clearing it emits subscription.updated.
inventory_wait reports a renewal waiting on stock under the invoice path of subscription_inventory_block_action: started_at, the blocked order_id, and its invoice_id. It clears when the renewal is paid or replaced, or the cycle is skipped, paused, or canceled. It appears on merchant reads only. Payment retries are refused while it is present, the same as during a delivery hold.
Needs attention#
GET /v1/subscriptions?needs_attention=true matches a subscription that is past_due or incomplete, active with a renewal more than 24 hours overdue, has a delivery_hold, upcoming_delivery_hold, or inventory_wait, or is paused with pause_reason: "delivery_action_required". needs_attention=false matches every other subscription. Both combine with the other filters. See Renewals that need attention.
Quantity, cadence, and items#
quantity multiplies the whole subscription. Each entry in line_items keeps its per-unit quantity, and each renewal order ships the two multiplied together. recurring_amount_money includes the multiplier. billing_interval and billing_interval_count are the cadence the buyer chose.
Change the cadence or quantity with PATCH /v1/subscriptions/{subscription_id}, within the plan's billing_interval_options and quantity_options. Add, change, and remove lines with POST /v1/subscriptions/{subscription_id}/line-items and PATCH or DELETE /v1/subscriptions/{subscription_id}/line-items/{subscription_line_item_id}. Changes apply from the next unpaid renewal. POST /v1/subscriptions/{subscription_id}/renew bills and ships the next shipment now. See Change cadence, quantity, and items and Order now.
A subscription created from an offer in a mixed cart has no subscription_plan_id. Its lines carry subscription_offer_id, and its options come from the offer.
Cycles, version, and filters#
completed_cycles counts the cycles charged so far, including any carried in with billing_start.imported.completed_cycles. Each renewal order's subscription_cycle is that count plus one. version goes up on every change. Send it as expected_version on PATCH, line item, and Order now requests, and on the buyer routes, to reject a change made after you read the subscription.
GET /v1/subscriptions filters by delivery_method_id to find every subscription using a method, and by hold_reason to find held subscriptions.
