Availability and capacity
Most booking systems keep a flat count per time slot and hope the integrations agree. Quikstik keeps a capacity chain and computes availability from it every time you ask.
The chain
Section titled “The chain”Capacity is a tree of pools. A departure (a specific variant at a specific start time) is a leaf. Above it sit the shared constraints that really limit a business — a fleet of 30 bikes shared by every product, a permit that allows 40 people in the park per hour, a boat, a guide roster. A pool has a target (sellable units) and an allowance (a fixed count or a percentage of target), and may have a parent.
A pool’s committed is derived from its leaves on every read, so re-parenting a pool or changing a target takes effect on the next read with nothing to rebuild.
Availability for a departure is the minimum of limit − committed over the leaf and every ancestor. That is the chain_remaining you see in a snapshot.
Withholding per lane
Section titled “Withholding per lane”Seats can be withheld from a lane: a tenant can keep the last four seats on each departure for direct sales while an OTA sees the rest. A snapshot’s basis reports chain_remaining, withheld (reserved for other demand classes) and offered = chain_remaining − withheld, so any reader can reconcile the storefront’s number against the pools without a database.
Reading availability
Section titled “Reading availability”POST /availability/snapshot-requests takes a variant and a time window and returns one line per departure in the window:
{ "offering_variant_id": "…", "start_time": "2026-11-05T14:00:00Z", "status": "available", "available_quantity": 6, "basis": { "chain_remaining": 6, "withheld": 0, "offered": 6 } }status is an open string: available, sold_out, and others as the catalog grows (inquire, unknown for an unconfigured line). A snapshot is a read. It allocates nothing and guarantees nothing; it is a faithful picture of the chain at generated_at_ms.
Operators can also read a whole day composed server-side — every departure with its parties, riders, readiness and balances — with GET /days/{date}.
Committing
Section titled “Committing”Capacity is committed by one writer, in one transaction, together with the booking’s roster and its money obligation. There is no second path that can commit a seat — not an OTA sync, not an import, not an admin tool. That single-writer rule is what makes the count trustworthy: if the chain cannot hold the seats the write fails 409 (sold_out on a purchase, capacity_full on a rebook, with the pool_key that was full) and nothing else happens — no charge, no roster, no email.
Order of operations on a purchase: commit the seats → price → charge → record. A processor decline after the commit releases the seats in the same transaction; a charge that succeeds never leaves a seat uncommitted.
Departures
Section titled “Departures”A departure row is shared by every booking on it (it is resolved from variant + start time). Facts that belong to one booking — an amended time, a delivery address, a named meeting place — live on that booking’s agreement, never on the departure. Operators can create a departure ahead of demand (POST /departure-materializations) and cancel one with every booking on it refunded in full (POST /departure-cancellations).
Provisioning the chain
Section titled “Provisioning the chain”POST /capacity-pools creates or replaces a pool’s configuration (a re-config preserves what is committed). POST /capacity-pools/{pool_key}/parent is the only sanctioned way to move a pool in the tree; self-parenting, cycles and unknown parents are refused 422. GET /capacity-pools lists the tenant’s pools with their derived committed counts.