Skip to main content

Cross-Club Fixtures

Fixtures connect a hosted, scheduled competition to one or more guest clubs (workspaces) without granting those clubs membership in the host workspace. The system supports invitation management, roster isolation, revision-based reacceptance, timeline logging by guest teams, and result correction. Legacy 100m fixtures retain their participant roster endpoints; multi-discipline fixtures use protected meet entrants and session registrations.

All paths are relative to /api/v1. Authentication is required for all routes.

Concepts​

  • Host workspace: the club that created the event and owns the fixture. Controls event lifecycle, can view all teams' entries/results, and can override any participant's result.
  • Guest workspace: a club that has accepted an invitation to participate. Can manage their own roster, timeline entries, and results only.
  • Fixture revision: incremented on material changes (date/time/venue changes, roster additions/removals). Guest teams must reaccept after a revision bump.
  • Withdrawal: a team may be withdrawn (by the guest before start, or by the host after start) without deleting historical data.

Host Endpoints​

Mounted at /events/:eventId (the fixtureHostRouter).

MethodPathPurpose
POST/events/:eventId/fixture-invitationsCreate a pending invitation to a target club
GET/events/:eventId/fixture-invitationsList invitations with response history
POST/events/:eventId/fixture-invitations/:invitationId/resendRevoke and replace a pending/declined invitation
DELETE/events/:eventId/fixture-invitations/:invitationIdRevoke an unused invitation
GET/events/:eventId/fixture-rostersRead participating-team rosters
GET/events/:eventId/fixture-entriesRead all teams' timeline entries
GET/events/:eventId/fixture-resultsRead all teams' results
PUT/events/:eventId/fixture-results/:athleteIdOverride any participant's result
POST/events/:eventId/fixture-workspaces/:workspaceId/withdrawalRecord a guest withdrawal after start

Create invitation​

POST /events/:eventId/fixture-invitations
Body: { targetClubId: UUID, expiresInDays?: number (default 7) }

Creates a pending invitation. The target club is resolved through the clubs table. The host cannot invite itself (409 FIXTURE_HOST_CANNOT_INVITE_SELF). Duplicate active invitations for the same club are rejected (409 FIXTURE_INVITATION_EXISTS). The event must be scheduled.

Resend invitation​

POST /events/:eventId/fixture-invitations/:invitationId/resend

Revokes the current token and creates a replacement invitation with a new token and 7-day expiry. Only works on pending, declined, or change_requested invitations.

Revoke invitation​

DELETE /events/:eventId/fixture-invitations/:invitationId

Sets status to revoked. Only works on pending, declined, or change_requested invitations.

Host roster read​

GET /events/:eventId/fixture-rosters

Returns { data: FixtureTeamRoster[] } with each team's participants. Uses the safe participant summary (name, archive status) — never exposes guest private athlete fields.

Host result override​

PUT /events/:eventId/fixture-results/:athleteId
Body: { manualOverride: number | null, overrideReason: string | null }

The host can override any participant's result, including guest-team athletes. This uses the same override path as the standard results endpoint.

Host withdrawal​

POST /events/:eventId/fixture-workspaces/:workspaceId/withdrawal

Records a guest withdrawal after the fixture has started. Preserves all participant, timeline, and result history.

Guest Endpoints​

Mounted at /fixtures (the fixtureGuestRouter).

MethodPathPurpose
GET/fixtures/incomingList pending invitations for the caller
POST/fixtures/incoming/:invitationId/respondAccept, decline, or request changes
GET/fixturesList guest fixtures
GET/fixtures/:eventIdGet fixture detail
GET/fixtures/:eventId/participantsList the guest team's participants
POST/fixtures/:eventId/participantsAdd an athlete to the guest roster
PUT/fixtures/:eventId/participants/:athleteIdUpdate RSVP status
DELETE/fixtures/:eventId/participants/:athleteIdRemove from guest roster
POST/fixtures/:eventId/withdrawalWithdraw before start
GET/fixtures/:eventId/entriesList the guest team's timeline entries
POST/fixtures/:eventId/entriesCreate a timeline entry
PATCH/fixtures/:eventId/entries/:entryIdEdit a timeline entry
DELETE/fixtures/:eventId/entries/:entryIdUndo a timeline entry
GET/fixtures/:eventId/resultsList the guest team's results
PUT/fixtures/:eventId/results/:athleteIdOverride the guest team's result

Invitation response​

POST /fixtures/incoming/:invitationId/respond
Body: { response: 'accepted' | 'declined' | 'change_requested', message?: string }

Accepting creates the guest workspace relationship and sets the accepted revision. Declining or requesting changes records the response with an optional message. The invitation must be pending or change_requested and not expired.

Guest roster management​

Guest roster routes enforce that the fixture is scheduled, the guest team is accepted, and the accepted revision matches the current fixture revision. Roster changes are locked after the fixture starts or after a material revision change.

Multi-discipline fixture rosters​

After accepting an invitation to a multi-discipline meet, a guest coach opens the shared event detail and uses its existing discipline tabs. The coach can create only active athletes from their own workspace and register each entrant only in the sessions required by that club. An athlete registration requires the session's catalogue definition to be in the athlete's preferred disciplines; a mismatched request returns 409 ATHLETE_DISCIPLINE_MISMATCH. A club with no 200m athletes, for example, simply creates no 200m registration. Hosts can see every accepted club's session entries, while guests see and change only their own.

Guest timeline logging​

Guest teams can create, edit, and undo timeline entries for their own athletes only. The same version-conflict, lifecycle, and result-recomputation rules apply as the standard timeline API.

Guest withdrawal​

POST /fixtures/:eventId/withdrawal

Available only while the fixture is scheduled. After start, only the host can record a withdrawal.

Invitation State Machine​

pending → accepted
pending → declined
pending → change_requested
pending → revoked
change_requested → accepted
change_requested → declined
change_requested → revoked

Expiry makes a pending or change_requested invitation unavailable.

Fixture Revision​

events.fixture_revision starts at 1 and is incremented when:

  • A material date/time/venue change is made
  • A guest team is accepted (first acceptance or after withdrawal)
  • A guest team is withdrawn

After a revision bump, all non-withdrawn guest teams are marked reacceptance_required and new invitation tokens are created. The host cannot start the fixture until all teams have accepted the current revision.

Notification System​

Fixture lifecycle events generate in-app notifications:

KindRecipientsTrigger
fixture_invitedCoaches in the target workspace (or email-matched users)Invitation created
fixture_respondedHost workspace coachesGuest responds to invitation
fixture_reacceptance_requiredGuest workspace coachesMaterial revision change
fixture_startedAccepted guest workspace coachesEvent moved to in_progress

Notifications are deduplicated via dedupe_key and support unread counts and mark-as-read.

Notification Endpoints​

MethodPathPurpose
GET/notificationsList all notifications for the caller
GET/notifications/unread-countGet count of unread notifications
POST/notifications/:notificationId/readMark a notification as read

Database Tables​

  • event_fixture_workspaces — workspace participation (host/guest, status, accepted revision)
  • fixture_invitations — email-bound or workspace-bound invitations with hashed tokens
  • fixture_invitation_responses — immutable response records with actor attribution
  • fixture_notifications — in-app notification delivery and read tracking

Authorization Rules​

  • Host authority: only the host can start, complete, cancel, or materially edit a fixture event
  • Guest isolation: guest teams can only manage their own roster, entries, and results
  • Result correction: the host can override any participant's result; guests can only override their own
  • Roster locking: roster changes require scheduled status, accepted team status, and matching revision
  • Post-start withdrawal: only the host can record a withdrawal after the fixture begins

AI declaration​

This document was created or updated with the assistance of OpenCode[openai/gpt-5.6-terra].