How to: a shared training log

An athlete often trains with more than one program at once: a school team and an outside training group are the common case. Both want the same log. The platform serves this with one writer and first-class readers by default (GR-07): a second write-holding grant for the same scope exists only when the athlete explicitly moves the writer, never automatically. Doubled rows are then expected, not forbidden (MW-04), but the default case below has exactly one writer.

Writer setup

Say the school team is the writer. It holds a Tier 2 token, and the athlete's grant gives it training_access: write (and checkins_access: write if it logs check-ins too). Every row it writes carries its own origin_team_id (ENT-08) and, when it sets one, its own external_ref (WR-02):

curl -X POST https://xcathletes.org/v1/training-sessions \
  -H "Authorization: Bearer $SCHOOL_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "person_id": "1a2b3c4d5e6f70819203a4b5c6d7e8f9",
    "date": "2026-08-20",
    "modality": [{ "system": "xca", "code": "skiing", "display": "Skiing" }],
    "duration_seconds": 3600,
    "external_ref": "school-2026-08-20-riley"
  }'

Reader setup

The training group holds a Tier 1 token. The athlete accepts its grant read-only, the default for every grant beyond the creating team's own (GR-07). A /v1/ list read carries no person filter this pass: it reads every row the token reaches, each one still carrying its own origin_team_id, so the reader labels the source in its own UI and narrows to one athlete client-side rather than at the query string:

curl -H "Authorization: Bearer $GROUP_TOKEN" \
  https://xcathletes.org/v1/training-sessions

Echo suppression: one comparison

If the training group also holds write access, deliberately or because the athlete moved the writer to it later, both sides poll the same change feed and both see rows the other side wrote, and rows they wrote themselves coming back around. SYNC-07 makes telling those apart one comparison: every feed entry carries origin_consumer_id, the team that made the change. When it equals your own team id, the entry is your own write echoing back through the feed, something you already have locally, and you skip re-applying it. When it doesn't, the row is genuinely someone else's, and you apply it as usual. No second lookup, no comparing external_ref values against your own store first; the one field decides it.

What checkin_conflict means here

Training sessions tolerate two write grants (MW-04); check-ins do not (ENT-07). Exactly one check-in exists per person per local calendar date, platform-wide, regardless of who logs it. If both the school and the training group's own system independently try to log the athlete's check-in for the same day, whichever POST lands second gets checkin_conflict:

HTTP/1.1 409 Conflict

{
  "type": "https://xcathletes.org/docs/errors/checkin_conflict",
  "title": "Check-in conflict",
  "status": 409,
  "detail": "A check-in already exists for this person on 2026-08-20 (MW-05, ENT-07); PATCH the existing check-in instead of creating another."
}

The losing side's next move is to GET the existing check-in, which will show the other team's origin_team_id, and treat it as the athlete's report already captured rather than retrying its own write blind.