Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ curl -X POST https://api.saturday.fit/v1/partner/api-keys \

`scopes` accepts `["*"]` for unrestricted, `["read"]` for `GET`/`HEAD`/`OPTIONS`, and `["write"]` for `POST`/`PUT`/`PATCH`/`DELETE`. A read-write key needs both listed: `write` does not imply `read`. A request outside a key's scopes gets a `403` with code `insufficient_scope`.

`environment` is optional and defaults to the environment of the host you call: `live` on `api.saturday.fit`, `test` on the sandbox host. Naming the other environment, or any other value, is a `400` with code `invalid_value`, because a key record only ever authenticates on the host that issued it.

<Note>
Save the key immediately. The full value is returned once at creation. The `id` in that response is what the rotate and revoke endpoints take, so store it with the key.
</Note>
Expand Down Expand Up @@ -154,4 +156,4 @@ Authentication and authorization failures share the standard [error envelope](/e
| `coach_tier_required` | `authorization_error` | 403 | A `cp_` key whose coach is below Pro Coach |
| `internal_error` | `api_error` | 503 | Key verification is temporarily unavailable. Retry rather than rotating a working key |

A key used against the wrong environment resolves as `invalid_api_key`, because the key does not exist in that environment's records.
A key used against the wrong environment fails closed as `invalid_api_key`, and the message names the environment the key was issued for. Each environment keeps separate key records, and a record whose environment is not the host's is refused even if it is present.
2 changes: 1 addition & 1 deletion guides/athletes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ The settings object is written as a unit. Send the athlete's complete settings o

The same replacement rule applies if you send a `settings` object through `PATCH /v1/athletes/{id}`.

On update and on calculate, Saturday rejects `carb_upper_limit_override` outside 50-150 g/hr and `weight_kg` outside 20-250 with a 400 rather than clamping, so a bad value in your data surfaces at the boundary instead of distorting a prescription.
On create, on update and on calculate, Saturday rejects `carb_upper_limit_override` outside 50-150 g/hr and `weight_kg` outside 20-250 with a `400` of code `out_of_range` rather than clamping, so a bad value in your data surfaces at the boundary instead of distorting a prescription. Batch create reports the same code per item.

## Deleting an athlete

Expand Down
4 changes: 3 additions & 1 deletion guides/coach-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ Coach API responses identify each athlete by Saturday UID, the same identifier t

## Rate limits

Coach requests, on either credential, are metered in the same partner bucket as the Claude connector. An OAuth coach token also passes through the per-athlete bucket that applies to every athlete-delegated caller, keyed on the coach's UID. A roster pull or digest is one request whatever the roster size. Every response carries the rate-limit headers and a `429` carries `Retry-After`; see [rate limiting](/rate-limiting).
Coach requests to `/v1/coach/*`, on either credential, are metered in a bucket of the coach's own: 50 requests per second sustained with a burst of 500, sized so a roster-wide fan-out never trips it. That bucket replaces the Claude connector's shared partner bucket and the per-athlete buckets on the coach surface. Off the coach surface, an OAuth coach token is metered as the athlete it also is, in the per-athlete buckets that apply to every athlete-delegated caller. A roster pull or digest is one request whatever the roster size. Every response carries the rate-limit headers and a `429` carries `Retry-After`; see [rate limiting](/rate-limiting).

A coach API key reaches `/v1/coach/*` only; on any other endpoint it is refused with `403 insufficient_scope`.

## SDKs

Expand Down
5 changes: 3 additions & 2 deletions guides/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Every delivery has this envelope. `id` is a UUID and `created_at` is a Unix time
"type": "prescription.calculated",
"created_at": 1765467600000,
"data": {
"athlete_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"tier": "full",
"carb_g_per_hr": 60,
"sodium_mg_per_hr": 500,
Expand Down Expand Up @@ -165,11 +166,11 @@ What `data` carries depends on the event:
| `athlete.deleted` | `athlete_id` only |
| `activity.created`, `activity.updated` | The activity record |
| `activity.deleted` | `athlete_id` and `activity_id` only |
| `prescription.calculated` | From `POST /v1/nutrition/calculate`: the calculate response, at whichever tier the caller received, teaser included. From an activity calculation: the stored prescription (`carb_g_per_hr`, `sodium_mg_per_hr`, `fluid_ml_per_hr`, the three totals, `calculated_at`) |
| `prescription.calculated` | From `POST /v1/nutrition/calculate`: the calculate response, at whichever tier the caller received, teaser included, plus `athlete_id` when the request named an athlete. From an activity calculation: the stored prescription (`carb_g_per_hr`, `sodium_mg_per_hr`, `fluid_ml_per_hr`, the three totals, `calculated_at`) plus `athlete_id` and `activity_id` |
| `feedback.submitted` | The feedback record: `rating`, `notes`, `created_at` |
| `subscription.created`, `subscription.cancelled` | The payloads above |

The prescription payload names no athlete or activity. A calculation you trigger through the API is identifiable by its response; the webhook is the signal that one ran.
A calculation with no athlete (a `POST /v1/nutrition/calculate` without `athlete_id`) carries neither id; `tier` tells you which tier the caller received.

Athlete, activity, and event identifiers are all UUIDs; they carry no prefix, so do not pattern-match on one.

Expand Down
Loading