Skip to main content

Organizations

Organizations let partners manage teams of athletes under a single billing structure. This is designed for coaching platforms, team managers, and enterprise partners who onboard athletes in groups. The examples read your key from a SATURDAY_API_KEY environment variable, as in Quickstart. Sandbox keys are issued with a separate base URL; using one against api.saturday.fit returns 401 invalid_api_key.

Creating an organization

The response includes server-set id, partner_id, created_at, updated_at (Unix seconds), and a maintained member_count. Organizations count against your account’s organization quota; at the cap the request returns 403 with code resource_limit_exceeded.

Managing members

Members are the organization’s people directory: coaches and staff, keyed by email. They are separate from athlete records, and linking a member row to an athlete via athlete_id is optional.

Adding members

The response is 201 with the member: id, email, role, athlete_id when linked, and joined_at in Unix seconds. email is required. Adding the same email twice creates two directory entries; the directory is not deduplicated.

Member roles

Any other value returns 400.

Listing and removing members

The list returns every member under members, newest first, with total. Removing a member never deletes an athlete profile; it removes the directory entry, decrements member_count, and returns 204.

Seat subscriptions (team licensing)

A seat subscription is a block of paid seats the organization holds; assigning a license to an athlete grants them full-precision API responses while the subscription is active and in its date window. This is the lane for orgs that pay for their athletes’ access directly (vs. athletes paying at personal checkout).

Create a seat block

type is "team" or "enterprise"; seats must be positive; start_date and end_date are both required, in Unix seconds, with end_date after start_date. The response is 201 with the subscription, including used_seats (starts at 0) and status (active). Manage with GET /v1/organizations/{org_id}/subscriptions, which returns them under subscriptions, and PATCH /v1/organizations/{org_id}/subscriptions/{sub_id}, which updates seats (positive), status (active, expired, or cancelled), and end_date.

Assign licenses to athletes

athlete_id is required per license and must name an athlete under your partner account (404 otherwise); email and name are display metadata. The request is checked as a whole before anything is written: more licenses than free seats returns 400 with code seat_limit_exceeded, and a subscription whose status is not active returns 400 with code subscription_inactive. The response is 201 with the written licenses under licenses and their count in added. Licensing writes org_id and the subscription onto the athlete record, and the athlete’s next API call returns full precision. GET /v1/organizations/{org_id}/subscriptions/{sub_id}/licenses lists every license record for the subscription, newest first, including removed ones with active set to false, so filter on active when you want the athletes the seat block covers today. DELETE /v1/organizations/{org_id}/subscriptions/{sub_id}/licenses/{athlete_id} deactivates the license, frees the seat, and returns 200 with athlete_id and removed. Access degrades on the athlete’s next call. It also clears org_id from the athlete record, so an athlete who should keep the organization’s checkout discount needs org_id asserted again afterwards.

Organization offers (negotiated discounts)

A coach or team admin can negotiate a discount their athletes receive at personal checkout, as in “Team Alpine athletes get 15% off Saturday.” You record it as the organization’s offer, and Saturday applies it automatically when an affiliated athlete follows a subscribe CTA from your app. Each organization has at most one active offer. PUT creates or replaces it. Previous offers are deactivated rather than deleted, so the document trail is the negotiation audit log.

Setting the offer

The response is 200 with the stored offer (id, org_id, active, created_at, and the fields you sent) plus stack_cap_percent, the hard ceiling on the total stacked discount (30).

Reading and removing the offer

Which athletes get it

The offer applies to athletes whose org_id field references this organization. Assert it when creating or updating the athlete:
org_id is partner-asserted, the same trust posture as partner_plan, and the organization must exist first: pointing at an org you have not created returns 400 rather than silently producing an athlete whose team discount never applies. A seat license is not required, since the discount is for the athlete’s purchase and not a license entitlement. Set org_id to an empty string or null to remove the affiliation.

Stacking with partner offers

If your platform also has a partner-level offer (e.g. bundle pricing for your annual subscribers), eligible athletes get both: the percents combine multiplicatively, and the total is hard-capped at 30%. A 20% partner offer combined with a 15% org offer gives 1 - (0.80 × 0.85) = 32%, which the cap reduces to 30%. Checkout applies one combined discount. See Freemium Model, How discounts stack for the full discount flow.

Listing organizations

Returns the organizations created by your partner account, newest first, under organizations, with a pagination object (total, has_more, next_cursor). limit defaults to 50 and is capped at 200; pass cursor from next_cursor for the next page.

Updating an organization

Send only the fields you are changing. id, partner_id, created_at, and member_count are server-owned and ignored.

Finding an organization’s athletes

Athletes reference their organization through the org_id field on the athlete record (POST /v1/athletes and PATCH /v1/athletes/{athlete_id}, see Organization offers above). To build a team roster view, keep an athlete-to-org mapping as you assert org_id, or for licensed teams read the seat-subscription license list and filter on active.