Subscription Entity
Managing recurring plans, seat-based addons, and lifecycle transitions without breaking legacy flows.
Subscription Entity
The Subscription object represents a recurring commercial agreement attached to a Customer. It combines plan pricing, addon quantities, billing cadence, and lifecycle state.
Usage
Use the Subscription entity to:
- Attach a pricing plan to a customer.
- Manage seat-based pricing through addon quantity.
- Apply global and addon-level discounts.
- Track metered usage when enabled.
- Execute lifecycle transitions (pause, resume, change, cancel-at-period-end).
- Keep amendment history for audits.
Seat-Based Pricing Model
In Donutwork, seat-based pricing is handled through addons.
- Configure the per-seat value in the addon price.
- Set the number of seats in
addons[].quantity. - Renewal totals automatically include seat addons.
Example:
- Addon
workspace_seatprice:12.00 - Quantity:
25 - Seat subtotal contribution:
300.00(before taxes/discounts)
Expected Subscription Flow
Plan Association
Attach a plan with POST /customers/{id}/subscriptions.json.
Runtime Configuration
Adjust addon quantities (including seats), discounts, and usage inputs as needed.
Lifecycle Transitions
Optionally apply lifecycle operations (pause, resume, change-plan, change-addons, cancel-at-period-end).
Renewal Boundary Processing
At next_renew, scheduled changes are applied and recurring billing is executed.
Lifecycle States and Transitions
| Lifecycle State | Meaning | Typical Entry |
|---|---|---|
active | Subscription is billable and renewable | Attach, resume, successful change |
paused | Billing is temporarily suspended | Pause action |
cancel_pending | Marked to stop at period boundary | Cancel-at-period-end |
cancelled | No longer active | Post-boundary stop/legacy cancellation |
trialing / past_due | Optional advanced states for integrations | External lifecycle orchestration |
State Definitions with Practical Examples
active: customer is billed normally at each renewal date. Example: a monthly plan renews on May 1, 2026 and remains active.paused: billing is temporarily halted. Example: customer asks to freeze service for 30 days; no renewal charge is attempted while paused.cancel_pending: subscription will stop at period boundary. Example: customer cancels on April 15, 2026; access remains until the next renewal boundary.cancel_pendingcan be reversed before boundary. Example: customer cancels, then changes mind after 3 days; use undo-cancel-at-period-end to return toactive.cancelled: subscription is fully closed. Example: boundary reached and cancellation finalized.trialing: customer is in free trial before first paid cycle. Example: plan includes 14-day trial after initial attach.past_due: payment issue detected and recovery flow is required. Example: renewal attempt failed and account is awaiting payment method update.
Pause and Resume Billing Semantics
When a subscription is paused, Donutwork stops renewal execution and clears next_renew while storing the previous renewal date in pause_state.previous_next_renew.
When resumed:
- If
resume_atis provided and is in the future, that date becomes the newnext_renew. - If
resume_atis missing, Donutwork uses the storedprevious_next_renew. - If the stored date is already in the past, Donutwork sets
next_renewto the resume day (today), not retroactively.
Practical timeline example:
- Subscription paused on May 1, 2026.
- Resumed on July 1, 2026 (after 2 months).
- Result: no automatic back-billing for May/June;
next_renewis re-anchored to July 1, 2026 (or a futureresume_atif provided).
Lifecycle Graph
Proration Rules
- Positive delta (upgrade): immediate debit charge.
- Negative delta (downgrade): credit stored in
carryover_credit. - Carryover credit is consumed in future renewals before charging new amount.
Scheduled Changes
For change-plan and change-addons, you can set when: period_end.
The request is stored in scheduled_change and applied automatically at renewal boundary.
Amendment History
Every lifecycle change is written to an immutable amendment stream. This supports support/debug/audit use cases with before/after snapshots and proration metadata.
Data Schema
| Field | Type | Description |
|---|---|---|
id | eid | Unique customer-subscription identifier. |
subscription_id | string | Plan identifier currently applied. |
status | string | Legacy status (active, dismiss, dismissed, ...). |
next_renew | date | null | Next billing boundary. |
addons | array | Addon entries with price and quantity (seat count included). |
global_discount | object | Plan-level discount descriptor. |
lifecycle | object | Advanced lifecycle state metadata. |
pricing_snapshot | object | Captured pricing context for lifecycle operations. |
scheduled_change | object | null | Deferred mutation to apply at renewal. |
pause_state | object | null | Pause/resume metadata. |
carryover_credit | number | Credit wallet generated by negative prorations. |
cancel_at_period_end | boolean | Explicit cancellation intent at renewal boundary. |
Backward Compatibility
No legacy route or payload shape is removed.
Legacy vs New lifecycle behavior
| Area | Legacy Behavior | New Lifecycle Behavior |
|---|---|---|
| Attach/Detach | Existing routes remain valid | Unchanged, plus additive lifecycle metadata |
| Addons update | Existing addon mutation works | Optional lifecycle change-addons with proration/scheduling |
| Status endpoint | Legacy status continues | Adds lifecycle visibility without removing legacy keys |
| Seat pricing | Addons with quantity | Same model, now explicitly documented as official seat model |
| Cancellation | dismiss based flow | cancel_at_period_end maps to compatible legacy status |