Product cycles
Cycles are cohorts, runs or versions of a product. They require API access and no additional plan feature. Archive cycles to keep existing references intact.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/products/:productId/cycles |
List cycles and seat counts |
| POST | /api/v1/products/:productId/cycles |
Create or update by case-insensitive name |
| GET | /api/v1/product-cycles/:id |
Get one cycle |
| PATCH | /api/v1/product-cycles/:id |
Update or archive |
List cycles
The list accepts is_archived (true or false), limit (default 50, cap 500) and offset (default 0). Omitting is_archived includes archived cycles. Dates sort ascending, undated cycles last, then creation time. The response is { data, total, limit, offset }.
Cycle records include id, workspace_id, product_id, the fields below, timestamps, sales_count and units_taken. units_taken counts the seats currently occupied — the units of every active place in the cycle (a cancelled sale or ended access frees its seats) — so compare it with capacity. A returned price can be a decimal string, number or null.
Create or update
| Field | Constraints |
|---|---|
name |
Required on POST; 1–200 chars. Case-insensitive, unique within this product. |
starts_on / ends_on |
Real YYYY-MM-DD workspace-calendar dates, or null. End cannot precede start. |
duration_unit |
days (default), weeks, months, years. |
manual_status |
Undated cycles: open, ongoing, ended, undated, cancelled, or null. With either date set, only cancelled or null is accepted; null resumes automatic status. |
price |
Number 0–1,000,000,000, at most 2 decimals, or null to use product price. Workspace payment currency. On a product with dynamic_pricing: false, a price different from the product's is refused with 400 PRODUCT_PRICE_LOCKED — see fixed pricing. |
dynamic_pricing |
Boolean, default true. false stops sales in this cycle from overriding the cycle's price. |
capacity |
Integer 1–1,000,000, or null for no cap. |
is_archived |
Boolean, default false. Archive with true; no DELETE endpoint. |
rearm_date_triggers |
Boolean, default false. See Date reminders. |
POST matches the name within the product and updates only supplied fields. It returns 201 with duplicate: true for a match or false for creation. PATCH makes every field optional and returns 200, without a duplicate marker.
Example POST body:
{
"name": "Autumn course",
"starts_on": "2026-10-01",
"ends_on": "2026-10-31",
"duration_unit": "weeks",
"capacity": 20
}
Unknown or cross-workspace products/cycles return 404. Invalid dates, reversed ranges and invalid manual statuses return 400. Renaming a cycle to another cycle's name returns 409 CYCLE_NAME_TAKEN.
Date reminders
Automations can fire a set time before or after a cycle's start or end date, once per person. When an update changes starts_on or ends_on after such reminders already fired, the API does not fire them again by default. Send rearm_date_triggers: true with the date change to have them fire again for the new date; the response then carries rearmed_fires — how many already-fired reminders were reset (0 = nothing to fire again). Only reminders anchored on the date that actually changed are reset. The flag works on PATCH and on a POST that matches an existing cycle.
Attach a cycle to a deal with cycle_id; it must belong to the attached product. The product's enforce_cycle_capacity and require_cycle options govern sales, while a deal's cycle remains optional.