> ## Documentation Index
> Fetch the complete documentation index at: https://docs.saturday.fit/llms.txt
> Use this file to discover all available pages before exploring further.

# Organizations

> Teams, member directories, seat licensing, and negotiated discounts

# 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.

## Creating an organization

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.saturday.fit/v1/organizations",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "display_name": "Seattle Running Club",
          "description": "Marathon training group",
          "sport": "run",
      },
  )

  org = response.json()  # 201 Created
  org_id = org["id"]
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.saturday.fit/v1/organizations", {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_test_abc123def456",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      display_name: "Seattle Running Club",
      description: "Marathon training group",
      sport: "run",
    }),
  });

  const org = await response.json(); // 201 Created
  const orgId = org.id;
  ```
</CodeGroup>

| Field          | Required | Description                                     |
| -------------- | -------- | ----------------------------------------------- |
| `display_name` | Yes      | Human-readable organization name                |
| `description`  | No       | Free text                                       |
| `sport`        | No       | Primary sport (e.g. `run`, `bike`, `triathlon`) |

The response includes server-set `id`, `created_at`, `updated_at`, and a maintained `member_count`.

## Managing members

Members are the organization's **people directory** (coaches and staff by email). They're separate from athlete records — linking a member row to an athlete via `athlete_id` is optional.

### Adding members

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(
      f"https://api.saturday.fit/v1/organizations/{org_id}/members",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "email": "coach@seattlerunclub.com",
          "role": "admin",
          "athlete_id": "a1b2c3d4-...",  # optional link to an athlete record
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    `https://api.saturday.fit/v1/organizations/${orgId}/members`,
    {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_abc123def456",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        email: "coach@seattlerunclub.com",
        role: "admin",
        athlete_id: "a1b2c3d4-...", // optional link to an athlete record
      }),
    }
  );
  ```
</CodeGroup>

### Member roles

| Role     | Meaning                                  |
| -------- | ---------------------------------------- |
| `admin`  | Organization manager (coach, team admin) |
| `member` | Everyone else                            |

### Listing and removing members

```bash theme={null}
GET /v1/organizations/{org_id}/members
DELETE /v1/organizations/{org_id}/members/{member_id}
```

Removing a member never deletes an athlete profile — it only removes the directory entry.

## 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

```bash theme={null}
POST /v1/organizations/{org_id}/subscriptions
```

```json theme={null}
{
  "type": "team",
  "seats": 25,
  "start_date": 1765467600,
  "end_date": 1797003600
}
```

`type` is `"team"` or `"enterprise"`; dates are unix seconds (`end_date` optional). The response includes `used_seats` (starts at 0) and `status`. Manage with `GET /v1/organizations/{org_id}/subscriptions` and `PATCH .../subscriptions/{sub_id}` (update `seats`, `status`, `end_date`).

### Assign licenses to athletes

```bash theme={null}
POST /v1/organizations/{org_id}/subscriptions/{sub_id}/licenses
```

```json theme={null}
{
  "licenses": [
    { "athlete_id": "a1b2c3d4-...", "email": "rider@example.com", "name": "Sam Rider" }
  ]
}
```

`athlete_id` is required per license (email/name are display metadata); the seat limit is enforced. The licensed athlete's next API call returns full precision. `GET .../licenses` lists assignments; `DELETE .../licenses/{athlete_id}` frees the seat (access degrades on the athlete's next call).

## Organization offers (negotiated discounts)

A coach or team admin can negotiate a discount their athletes receive at **personal checkout** — "Team Alpine athletes get 15% off Saturday." You record it as the organization's offer; 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, never deleted — the document trail is the negotiation audit log).

### Setting the offer

```bash theme={null}
PUT /v1/organizations/{org_id}/offer
```

<CodeGroup>
  ```python Python theme={null}
  response = requests.put(
      f"https://api.saturday.fit/v1/organizations/{org_id}/offer",
      headers={"Authorization": "Bearer sk_test_abc123def456"},
      json={
          "discount_percent": 15,
          "applies_to_plan": "12_month",
          "negotiated_by": "Coach Dana Reyes",
          "note": "2026 season team agreement",
      },
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    `https://api.saturday.fit/v1/organizations/${orgId}/offer`,
    {
      method: "PUT",
      headers: {
        Authorization: "Bearer sk_test_abc123def456",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        discount_percent: 15,
        applies_to_plan: "12_month",
        negotiated_by: "Coach Dana Reyes",
        note: "2026 season team agreement",
      }),
    }
  );
  ```
</CodeGroup>

| Field                   | Required | Description                                                                                                                                         |
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `discount_percent`      | Yes      | `1`–`30`. 30 is the stack cap (see below) — offers above the cap are rejected up front so you never promise athletes a number checkout won't honor. |
| `applies_to_plan`       | No       | `"12_month"`, `"1_month"`, or omit to apply to all plans                                                                                            |
| `negotiated_by`         | No       | Human label for the audit trail (coach name) — informational, not authentication                                                                    |
| `starts_at` / `ends_at` | No       | Unix **seconds**. Omit either for an open-ended window                                                                                              |
| `note`                  | No       | Free text — why this discount exists                                                                                                                |

The response echoes the stored offer plus `stack_cap_percent`, the hard ceiling on the total stacked discount (currently `30`).

### Reading and removing the offer

```bash theme={null}
GET /v1/organizations/{org_id}/offer      # current active offer; 404 when none
DELETE /v1/organizations/{org_id}/offer   # deactivates it; idempotent, returns 204
```

### Which athletes get it

The offer applies to athletes whose `org_id` field references this organization. Assert it when creating or updating the athlete:

```bash theme={null}
PATCH /v1/athletes/{athlete_id}
```

```json theme={null}
{
  "org_id": "org_abc123"
}
```

`org_id` is partner-asserted (the same trust posture as `partner_plan`) and the organization must already exist. A seat license is **not** required — the discount is for the athlete's own purchase, not a license entitlement. Set `org_id` to an empty string 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%**.

```
20% partner offer + 15% org offer
→ 1 − (0.80 × 0.85) = 32% → capped to 30%
```

Checkout applies one combined discount, and Saturday's subscribe landing page shows the stacked percent — the exact number the athlete will pay — along with which sources contributed. See [Freemium Model → Bundle offers](/guides/freemium-model#bundle-offers) for the full discount flow.

## Listing organizations

```bash theme={null}
GET /v1/organizations
```

Returns all organizations created by your partner account.

## Updating an organization

```bash theme={null}
PATCH /v1/organizations/{org_id}
```

```json theme={null}
{
  "display_name": "Seattle Running Club - Elite Squad",
  "description": "Elite marathon squad"
}
```

## Finding an organization's athletes

Athletes reference their organization through the `org_id` field on the athlete record (`POST`/`PATCH /v1/athletes`, see [Organization offers](#organization-offers-negotiated-discounts) above). To build a team roster view, track your own athlete→org mapping when you assert `org_id`, or use the seat-subscription [license list](#assign-licenses-to-athletes) for licensed teams — `GET .../licenses` returns exactly the athletes the org covers.
