Every operation starts from https://gorally.games/v1 and requires a key in the Authorization header, unless stated otherwise.

No sandbox

An rk_live_ key acts on your organization’s real data. Start with a read key, which cannot change anything.

Server to server only

The API does not answer browsers: it allows no CORS origin. Never put your key in browser-side code.

Leagues plan required

Issuing a key requires your organization’s Leagues plan. You then create your key in the Integrations screen.See the Leagues plan

Interactive explorer. Browse the contract in an interactive view. Sending requests is turned off.

Competition

Leagues, seasons, categories, standings.

This document, in English.

GET /openapi.json

No key required

No key required: the contract can be read before integrating.

Responses

Responses — GET /openapi.json
StatusDescription
200The OpenAPI document, in English.object
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.

Response example

{
  "openapi": "3.1.0",
  "info": {
    "title": "Rally Leagues API",
    "version": "1.0.0"
  }
}

This document, in French.

GET /openapi.fr.json

No key required

No key required, like the English version.

Responses

Responses — GET /openapi.fr.json
StatusDescription
200The OpenAPI document, in French.object
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.

Response example

{
  "openapi": "3.1.0",
  "info": {
    "title": "Rally — API des ligues",
    "version": "1.0.0"
  }
}

The organization's leagues.

GET /leagues

Scope: read

Cursor pagination on an immutable key: an existing row is seen exactly once, even under concurrent updates.

Parameters

Parameters — GET /leagues
NameLocationTypeRequiredDescription
limitqueryintegerminimum: 1maximum: 100default: 25NoPage size. Above 100, the request is refused rather than silently truncated.
afterquerystringNoCursor: the identifier of the LAST item of the previous page. An unknown cursor is refused — it is not recovered. The three paginated lists — events, leagues, matches — follow the SAME rule: traversal is ordered on an internal insertion number that no update moves, and a page stops before any write whose transaction may still be in flight. A row written by a long transaction is therefore DELAYED, never skipped: it appears in its place as soon as its transaction commits, and exactly once. Renaming a league or correcting a match between two pages neither hides it nor brings it back.

Responses

Responses — GET /leagues
StatusDescription
200A page of leagues.list<League>
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "list",
  "data": [
    {
      "id": "5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f",
      "name": "Ligue du mardi",
      "sport": "CURLING",
      "sportLabel": null,
      "profile": {
        "key": "CURLING",
        "label": "Curling",
        "version": 3,
        "resourceKind": "SHEET",
        "cadence": {
          "unitsPerMatch": 8,
          "minutesPerUnit": 15,
          "matchesPerNight": 1,
          "scoreTarget": null,
          "seasonWeeks": {
            "min": 10,
            "max": 30
          }
        },
        "vocabulary": {
          "resource": {
            "one": "feuille",
            "many": "feuilles",
            "genre": "F"
          },
          "unit": {
            "one": "bout",
            "many": "bouts",
            "genre": "M"
          },
          "match": {
            "one": "partie",
            "many": "parties",
            "genre": "F"
          },
          "scoreUnit": {
            "one": "point",
            "many": "points",
            "genre": "M"
          }
        }
      },
      "timeZone": "America/Toronto"
    }
  ],
  "has_more": false
}

Creates a league.

POST /leagues

Scope: write

The organization's commercial entitlement is checked as for any write.

Parameters

Parameters — POST /leagues
NameLocationTypeRequiredDescription
Idempotency-KeyheaderstringmaxLength: 255NoReplays the original response for 24 hours: a success, or a refusal returned during execution. A body rejected before that (malformed JSON, invalid field) does not reserve the key. Same key with a different body: 409 IDEMPOTENCY_KEY_REUSED. An execution still in flight answers 409 IDEMPOTENT_REQUEST_IN_FLIGHT. A 5xx is never stored: it means "retry". And if an execution is interrupted with an unknown outcome — a connection lost right after the commit returns the same error as one lost right before — the replay answers 409 IDEMPOTENCY_OUTCOME_UNKNOWN rather than risk a duplicate: read the resource again, then resume with a new key.

Request body

CreateLeague

Request example

{
  "name": "Ligue du mardi",
  "sport": "CURLING",
  "timeZone": "America/Toronto"
}

Responses

Responses — POST /leagues
StatusDescription
201The created league.League
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
409Idempotency key replayed with a different body.
413Body over 16 KiB (BODY_TOO_LARGE). The public API carries no files.
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "id": "5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f",
  "name": "Ligue du mardi",
  "sport": "CURLING",
  "sportLabel": null,
  "profile": {
    "key": "CURLING",
    "label": "Curling",
    "version": 3,
    "resourceKind": "SHEET",
    "cadence": {
      "unitsPerMatch": 8,
      "minutesPerUnit": 15,
      "matchesPerNight": 1,
      "scoreTarget": null,
      "seasonWeeks": {
        "min": 10,
        "max": 30
      }
    },
    "vocabulary": {
      "resource": {
        "one": "feuille",
        "many": "feuilles",
        "genre": "F"
      },
      "unit": {
        "one": "bout",
        "many": "bouts",
        "genre": "M"
      },
      "match": {
        "one": "partie",
        "many": "parties",
        "genre": "F"
      },
      "scoreUnit": {
        "one": "point",
        "many": "points",
        "genre": "M"
      }
    }
  },
  "timeZone": "America/Toronto"
}

A league and its seasons.

GET /leagues/{leagueId}

Scope: read

Parameters

Parameters — GET /leagues/{leagueId}
NameLocationTypeRequiredDescription
leagueIdpathstringYes

Responses

Responses — GET /leagues/{leagueId}
StatusDescription
200The league and its seasons.LeagueDetail
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "league",
  "id": "5b0c2f9e-8a41-4d6b-9f3e-1a2b3c4d5e6f",
  "name": "Ligue du mardi",
  "sport": "CURLING",
  "sportLabel": null,
  "profile": {
    "key": "CURLING",
    "label": "Curling",
    "version": 3,
    "resourceKind": "SHEET",
    "cadence": {
      "unitsPerMatch": 8,
      "minutesPerUnit": 15,
      "matchesPerNight": 1,
      "scoreTarget": null,
      "seasonWeeks": {
        "min": 10,
        "max": 30
      }
    },
    "vocabulary": {
      "resource": {
        "one": "feuille",
        "many": "feuilles",
        "genre": "F"
      },
      "unit": {
        "one": "bout",
        "many": "bouts",
        "genre": "M"
      },
      "match": {
        "one": "partie",
        "many": "parties",
        "genre": "F"
      },
      "scoreUnit": {
        "one": "point",
        "many": "points",
        "genre": "M"
      }
    }
  },
  "timeZone": "America/Toronto",
  "cadences": [
    {
      "id": "7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f",
      "key": "HIVER",
      "label": "Hiver",
      "position": 1
    }
  ],
  "seasons": [
    {
      "id": "c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e",
      "name": "Hiver 2027",
      "cadenceId": "7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f",
      "ordinal": 1,
      "status": "SETUP",
      "sportProfile": "CURLING",
      "sportProfileVersion": 3,
      "resourceKind": "SHEET",
      "cadence": {
        "unitsPerMatch": 8,
        "minutesPerUnit": 15,
        "matchesPerNight": 1,
        "scoreTarget": null,
        "seasonWeeks": {
          "min": 10,
          "max": 30
        }
      },
      "vocabulary": {
        "resource": {
          "one": "feuille",
          "many": "feuilles",
          "genre": "F"
        },
        "unit": {
          "one": "bout",
          "many": "bouts",
          "genre": "M"
        },
        "match": {
          "one": "partie",
          "many": "parties",
          "genre": "F"
        },
        "scoreUnit": {
          "one": "point",
          "many": "points",
          "genre": "M"
        }
      }
    }
  ]
}

Creates a season in a league.

POST /leagues/{leagueId}/seasons

Scope: write

Parameters

Parameters — POST /leagues/{leagueId}/seasons
NameLocationTypeRequiredDescription
leagueIdpathstringYes
Idempotency-KeyheaderstringmaxLength: 255NoReplays the original response for 24 hours: a success, or a refusal returned during execution. A body rejected before that (malformed JSON, invalid field) does not reserve the key. Same key with a different body: 409 IDEMPOTENCY_KEY_REUSED. An execution still in flight answers 409 IDEMPOTENT_REQUEST_IN_FLIGHT. A 5xx is never stored: it means "retry". And if an execution is interrupted with an unknown outcome — a connection lost right after the commit returns the same error as one lost right before — the replay answers 409 IDEMPOTENCY_OUTCOME_UNKNOWN rather than risk a duplicate: read the resource again, then resume with a new key.

Request body

CreateSeason

Request example

{
  "name": "Hiver 2027",
  "cadenceId": "7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f",
  "startsAt": "2027-01-05T00:00:00.000Z",
  "endsAt": "2027-04-27T00:00:00.000Z"
}

Responses

Responses — POST /leagues/{leagueId}/seasons
StatusDescription
201The created season.Season
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
409Idempotency key replayed with a different body.
413Body over 16 KiB (BODY_TOO_LARGE). The public API carries no files.
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "id": "c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e",
  "name": "Hiver 2027",
  "cadenceId": "7e4d2c1b-0a9f-4e8d-b7c6-5a4b3c2d1e0f",
  "ordinal": 1,
  "status": "SETUP",
  "sportProfile": "CURLING",
  "sportProfileVersion": 3,
  "resourceKind": "SHEET",
  "cadence": {
    "unitsPerMatch": 8,
    "minutesPerUnit": 15,
    "matchesPerNight": 1,
    "scoreTarget": null,
    "seasonWeeks": {
      "min": 10,
      "max": 30
    }
  },
  "vocabulary": {
    "resource": {
      "one": "feuille",
      "many": "feuilles",
      "genre": "F"
    },
    "unit": {
      "one": "bout",
      "many": "bouts",
      "genre": "M"
    },
    "match": {
      "one": "partie",
      "many": "parties",
      "genre": "F"
    },
    "scoreUnit": {
      "one": "point",
      "many": "points",
      "genre": "M"
    }
  }
}

The categories of a season.

GET /seasons/{seasonId}/categories

Scope: read

Parameters

Parameters — GET /seasons/{seasonId}/categories
NameLocationTypeRequiredDescription
seasonIdpathstringYes

Responses

Responses — GET /seasons/{seasonId}/categories
StatusDescription
200The season's categories, returned in full.list<Category>
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "list",
  "data": [
    {
      "id": "e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f",
      "seasonId": "c3a1e5d7-9b2f-4a6c-8e0d-1f3b5d7a9c2e",
      "name": "Mardi soir",
      "phase": "REGULAR",
      "revision": 6,
      "policyRevision": 1,
      "matchCount": 28,
      "unplayedMatchCount": 20,
      "unplayedRegularMatchCount": 20,
      "registrationCapacity": 8
    }
  ]
}

The standings of a category.

GET /categories/{categoryId}/standings

Scope: read

Standings are COMPUTED on read and never stored: that is why there is no standings.updated webhook. A consumer who wants to follow them listens to match.completed and reads this again.

Parameters

Parameters — GET /categories/{categoryId}/standings
NameLocationTypeRequiredDescription
categoryIdpathstringYes

Responses

Responses — GET /categories/{categoryId}/standings
StatusDescription
200The standings, ordered and returned in full.list<StandingRow>
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "list",
  "data": [
    {
      "leagueTeamId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Les Pierres",
      "rank": 1,
      "tied": false,
      "played": 4,
      "wins": 3,
      "draws": 0,
      "losses": 1,
      "scoreFor": 29,
      "scoreAgainst": 18,
      "diff": 11,
      "gamePointsFor": 0,
      "gamePointsAgainst": 0,
      "points": 9
    },
    {
      "leagueTeamId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "name": "Les Balais",
      "rank": 2,
      "tied": false,
      "played": 4,
      "wins": 2,
      "draws": 1,
      "losses": 1,
      "scoreFor": 24,
      "scoreAgainst": 21,
      "diff": 3,
      "gamePointsFor": 0,
      "gamePointsAgainst": 0,
      "points": 7
    }
  ]
}

Results

Matches and result entry.

The organization's matches.

GET /matches

Scope: read

revision is the optimistic lock to send when writing a result.

Parameters

Parameters — GET /matches
NameLocationTypeRequiredDescription
limitqueryintegerminimum: 1maximum: 100default: 25NoPage size. Above 100, the request is refused rather than silently truncated.
afterquerystringNoCursor: the identifier of the LAST item of the previous page. An unknown cursor is refused — it is not recovered. The three paginated lists — events, leagues, matches — follow the SAME rule: traversal is ordered on an internal insertion number that no update moves, and a page stops before any write whose transaction may still be in flight. A row written by a long transaction is therefore DELAYED, never skipped: it appears in its place as soon as its transaction commits, and exactly once. Renaming a league or correcting a match between two pages neither hides it nor brings it back.

Responses

Responses — GET /matches
StatusDescription
200A page of matches.list<Match>
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "list",
  "data": [
    {
      "id": "f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
      "categoryId": "e2b4d6f8-1a3c-4e5b-9d7f-0c2e4a6b8d1f",
      "status": "SCHEDULED",
      "teamAId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "teamBId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "scoreA": null,
      "scoreB": null,
      "sets": null,
      "startsAt": "2027-01-13T00:15:00.000Z",
      "revision": 3
    }
  ],
  "has_more": true
}

Writes or corrects the result of a match.

PATCH /matches/{matchId}

Scope: write

expectedRevision is REQUIRED: this compare-and-swap is what stops two programs from silently overwriting each other's result. A stale revision returns 409 — read the match again, do not retry blindly. No Idempotency-Key here: the revision lock already does that job, and two mechanisms would contradict each other.

Parameters

Parameters — PATCH /matches/{matchId}
NameLocationTypeRequiredDescription
matchIdpathstringYes

Request body

MatchResult

Request example

{
  "expectedRevision": 3,
  "event": "COMPLETER",
  "score": {
    "a": 7,
    "b": 5,
    "unitsPlayed": 8
  }
}

Responses

Responses — PATCH /matches/{matchId}
StatusDescription
200The match after the write.object
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
409Stale revision, or a state that does not accept this command.
413Body over 16 KiB (BODY_TOO_LARGE). The public API carries no files.
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "id": "f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
  "status": "COMPLETED",
  "scoreA": 7,
  "scoreB": 5,
  "resultSide": "A",
  "sets": null,
  "revision": 4
}

Rosters

Under members:* scopes only — the only surface that carries people's names.

The roster of a registration.

GET /registrations/{registrationId}/roster

Scope: members:read

This is the ONLY family of routes that returns people's labels, and an organization can deny it to a key without taking away the rest. No other part of the API — webhooks included — copies them.

Parameters

Parameters — GET /registrations/{registrationId}/roster
NameLocationTypeRequiredDescription
registrationIdpathstringYes

Responses

Responses — GET /registrations/{registrationId}/roster
StatusDescription
200The roster.Roster
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "registrationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "label": "Les Pierres",
  "memberships": [
    {
      "personId": "3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a",
      "label": {
        "kind": "NAME",
        "value": "Camille Roy"
      },
      "role": "CAPTAIN",
      "jerseyNumber": 7,
      "endedAt": null
    }
  ]
}

Adds a person to a roster.

POST /registrations/{registrationId}/roster

Scope: members:write

Parameters

Parameters — POST /registrations/{registrationId}/roster
NameLocationTypeRequiredDescription
registrationIdpathstringYes
Idempotency-KeyheaderstringmaxLength: 255NoReplays the original response for 24 hours: a success, or a refusal returned during execution. A body rejected before that (malformed JSON, invalid field) does not reserve the key. Same key with a different body: 409 IDEMPOTENCY_KEY_REUSED. An execution still in flight answers 409 IDEMPOTENT_REQUEST_IN_FLIGHT. A 5xx is never stored: it means "retry". And if an execution is interrupted with an unknown outcome — a connection lost right after the commit returns the same error as one lost right before — the replay answers 409 IDEMPOTENCY_OUTCOME_UNKNOWN rather than risk a duplicate: read the resource again, then resume with a new key.

Request body

AddMembership

Request example

{
  "personId": "3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a",
  "role": "CAPTAIN",
  "jerseyNumber": 7
}

Responses

Responses — POST /registrations/{registrationId}/roster
StatusDescription
200The roster after the addition.Roster
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
409Idempotency key replayed with a different body, or a state conflict.
413Body over 16 KiB (BODY_TOO_LARGE). The public API carries no files.
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "registrationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "label": "Les Pierres",
  "memberships": [
    {
      "personId": "3c5e7a9b-1d2f-4b6c-8a0e-2d4f6b8c0e1a",
      "label": {
        "kind": "NAME",
        "value": "Camille Roy"
      },
      "role": "CAPTAIN",
      "jerseyNumber": 7,
      "endedAt": null
    }
  ]
}

Events

The same feed as webhooks, readable and replayable.

Structural events, oldest first.

GET /events

Scope: read

CATCH-UP: this is what prevents "we lost two days of webhooks". The feed is exactly the webhook feed — same allowlist, same thin payload, same identifiers (webhook-id equals id). No person, governance or payment event appears in it.

Parameters

Parameters — GET /events
NameLocationTypeRequiredDescription
limitqueryintegerminimum: 1maximum: 100default: 25NoPage size. Above 100, the request is refused rather than silently truncated.
afterquerystringNoCursor: the identifier of the LAST item of the previous page. An unknown cursor is refused — it is not recovered. The three paginated lists — events, leagues, matches — follow the SAME rule: traversal is ordered on an internal insertion number that no update moves, and a page stops before any write whose transaction may still be in flight. A row written by a long transaction is therefore DELAYED, never skipped: it appears in its place as soon as its transaction commits, and exactly once. Renaming a league or correcting a match between two pages neither hides it nor brings it back.

Responses

Responses — GET /events
StatusDescription
200A page of events.list<Event>
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "object": "list",
  "data": [
    {
      "id": "8f6e4d2c-0b9a-4c7e-a5d3-1f9e7c5a3b0d",
      "type": "match.completed",
      "created": "2027-01-13T03:12:45.000Z",
      "organization": "9d3f5a7c-2b1e-4c8d-a6f0-3e2d1c0b9a87",
      "data": {
        "object": "match",
        "id": "f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
        "revision": 4
      }
    }
  ],
  "has_more": false
}

Reads again an event received by webhook.

GET /events/{eventId}

Scope: read

An event outside the allowlist is NOT FOUND, not forbidden: it is not part of the public contract.

Parameters

Parameters — GET /events/{eventId}
NameLocationTypeRequiredDescription
eventIdpathstringYes

Responses

Responses — GET /events/{eventId}
StatusDescription
200The event.Event
400Malformed request (INVALID_INPUT).
401Missing, malformed, revoked or expired key (UNAUTHORIZED). Session cookies are worthless here.
403The key lacks the required scope (FORBIDDEN), or the organization's subscription does not allow this access (ENTITLEMENT_DENIED).
404Resource not found in the key's organization (NOT_FOUND).
429Too many requests (RATE_LIMITED), per address or per key. Wait the number of seconds given by Retry-After, then resume with jittered exponential backoff.
503Unavailable. LEAGUES_UNAVAILABLE (outage or 10 s deadline exceeded) carries Retry-After: 30; a write may then be unconfirmed, so check its state before repeating it. ENTITLEMENT_UNAVAILABLE and HOLDER_UNINITIALIZED: commercial entitlement unavailable or subscription holder not initialized.

Response example

{
  "id": "8f6e4d2c-0b9a-4c7e-a5d3-1f9e7c5a3b0d",
  "type": "match.completed",
  "created": "2027-01-13T03:12:45.000Z",
  "organization": "9d3f5a7c-2b1e-4c8d-a6f0-3e2d1c0b9a87",
  "data": {
    "object": "match",
    "id": "f4e3d2c1-b0a9-4f8e-8d7c-6b5a4f3e2d1c",
    "revision": 4
  }
}

Schemas

The objects that operations accept and return.

Error

Error
NameTypeRequiredDescription
errorstringYesHuman-readable message, in French. Derived from the code alone: it never carries an internal identifier.
codestringYesValues: UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, INVALID_INPUT, IDEMPOTENCY_KEY_REUSED, IDEMPOTENT_REQUEST_IN_FLIGHT, IDEMPOTENCY_OUTCOME_UNKNOWN, ENTITLEMENT_DENIED, ENTITLEMENT_UNAVAILABLE, HOLDER_UNINITIALIZED, BODY_TOO_LARGE, WEBHOOK_VAULT_UNAVAILABLE, ROTATION_EN_COURS, LEAGUES_UNAVAILABLE, RATE_LIMITED
request_idstringYesThe identifier of THIS request, also returned in the X-Request-Id header on every response — successes included. Copy it into a support ticket: it is what lets us find your request in our logs. If you send one yourself in X-Request-Id, it is echoed as is (8 to 64 characters of A-Za-z0-9_.:-).

SportTerm

SportTerm
NameTypeRequiredDescription
onestringYes
manystringYes
genrestringYesValues: M, F

SportCadence

Editable profile benchmarks; null values claim no default.

SportCadence
NameTypeRequiredDescription
unitsPerMatchinteger | nullminimum: 1Yes
minutesPerUnitinteger | nullminimum: 1Yes
matchesPerNightinteger | nullminimum: 1Yes
scoreTargetinteger | nullminimum: 1Yes
seasonWeeksobject | nullYes
seasonWeeks.minintegerminimum: 1Yes
seasonWeeks.maxintegerminimum: 1Yes

SportProfile

The league's RESOLVED sport profile. label is the catalogue label, or the sport entered by the club when the key is AUTRE. version is that of the catalogue that produced this profile: it moves forward when a returned value changes, never retroactively. Labels and vocabulary are in French.

SportProfile
NameTypeRequiredDescription
keystringYesValues: CURLING, CORNHOLE, HOCKEY_RECREATIF, VOLLEYBALL, PICKLEBALL, BALLE_MOLLE, AUTRE
labelstringYes
versionintegerYes
resourceKindstringYesValues: SHEET, COURT, FIELD, SURFACE
cadenceSportCadenceYes
vocabularyobjectYesThe sport's words: the resource a time slot books (sheet, board, field), the unit a match counts (end, inning, period), what is played and what is scored.
vocabulary.resourceSportTermYes
vocabulary.unitSportTermYes
vocabulary.matchSportTermYes
vocabulary.scoreUnitSportTermYes

League

League
NameTypeRequiredDescription
idstringYes
namestringYes
sportstringYesValues: CURLING, CORNHOLE, HOCKEY_RECREATIF, VOLLEYBALL, PICKLEBALL, BALLE_MOLLE, AUTRE
sportLabelstring | nullYesThe sport entered by the club when sport is AUTRE; null otherwise.
profileSportProfileYes
timeZonestringYes

LeagueDetail

LeagueDetail
NameTypeRequiredDescription
objectstringYesValues: league
idstringYes
namestringYes
sportstringYesValues: CURLING, CORNHOLE, HOCKEY_RECREATIF, VOLLEYBALL, PICKLEBALL, BALLE_MOLLE, AUTRE
sportLabelstring | nullYes
profileSportProfileYes
timeZonestringYes
cadencesCadence[]Yes
seasonsSeason[]Yes

Cadence

Cadence
NameTypeRequiredDescription
idstringYes
keystringYes
labelstringYes
positionintegerYes

CreateLeague

CreateLeague
NameTypeRequiredDescription
namestringmaxLength: 200Yes
sportstringYesA key from the sport profile catalogue. AUTRE for a sport without presets.Values: CURLING, CORNHOLE, HOCKEY_RECREATIF, VOLLEYBALL, PICKLEBALL, BALLE_MOLLE, AUTRE
sportLabelstring | nullmaxLength: 60NoThe sport's name, REQUIRED when sport is AUTRE and refused otherwise.
timeZonestringYesIANA time zone, for example America/Toronto.

Season

Season
NameTypeRequiredDescription
idstringYes
namestringYes
cadenceIdstringNo
ordinalintegerNo
statusstringNoValues: SETUP, REGULAR, PLAYOFFS, CLOSED, ABANDONED
sportProfilestring | nullYesLiteral key frozen at creation; null before the catalogue existed. A future key is still returned.
sportProfileVersioninteger | nullminimum: 1Yes
resourceKindstringYesValues: SHEET, COURT, FIELD, SURFACE
cadenceSportCadenceYes
vocabularyobjectYes
vocabulary.resourceSportTermYes
vocabulary.unitSportTermYes
vocabulary.matchSportTermYes
vocabulary.scoreUnitSportTermYes

CreateSeason

CreateSeason
NameTypeRequiredDescription
namestringmaxLength: 200Yes
cadenceIdstringYes
startsAtstring | nullformat: date-timeNo
endsAtstring | nullformat: date-timeNo

Category

Category
NameTypeRequiredDescription
idstringYes
seasonIdstringYes
namestringYes
phasestringYesValues: SETUP, REGULAR, PLAYOFFS, CLOSED, ABANDONED
revisionintegerYes
policyRevisionintegerNo
matchCountintegerNo
unplayedMatchCountintegerNo
unplayedRegularMatchCountintegerNo
registrationCapacityinteger | nullNo

StandingRow

StandingRow
NameTypeRequiredDescription
leagueTeamIdstringNo
namestringNo
rankintegerNo
tiedbooleanNo
playedintegerNo
winsintegerNo
drawsintegerNo
lossesintegerNo
scoreForintegerNo
scoreAgainstintegerNo
diffintegerNo
gamePointsForintegerNoGame points scored, summed over the set details. 0 outside the SETS result shape.
gamePointsAgainstintegerNo
pointsintegerNo

Match

Match
NameTypeRequiredDescription
idstringYes
categoryIdstringNo
statusstringNo
teamAIdstring | nullNo
teamBIdstring | nullNo
scoreAinteger | nullNo
scoreBinteger | nullNo
setsSetScore[] | nullNoSet details under the SETS result shape; null elsewhere, empty array on a forfeit. scoreA and scoreB then carry the SETS won.
startsAtstring | nullformat: date-timeNo
revisionintegerYesThe optimistic lock to send in expectedRevision.

MatchResult

MatchResult
NameTypeRequiredDescription
expectedRevisionintegerYes
eventstringYesValues: COMPLETER, ATTRIBUER, CORRIGER_SCORE, CORRIGER_ATTRIBUTION
scoreobjectNo
score.aintegerNo
score.bintegerNo
score.unitsPlayedintegerNo
setsSetScore[]maxItems: 15NoSet details, required under the SETS result shape and refused elsewhere. The match score is not sent: it is derived from the details. A policy cannot require more than 15 sets, so no result carries more.
resultSidestring | nullNoValues: A, B, DRAW
forfeitingSidestring | nullNoValues: A, B, BOTH
outcomestring | nullNoValues: PLAYED, CONCEDED, FORFEIT, DOUBLE_FORFEIT, ABANDONED
reasonstring | nullminLength: 1maxLength: 500No

SetScore

SetScore
NameTypeRequiredDescription
aintegerYes
bintegerYes

Roster

Roster
NameTypeRequiredDescription
registrationIdstringNo
labelstringNo
membershipsobject[]No

AddMembership

AddMembership
NameTypeRequiredDescription
personIdstringYes
rolestringNoValues: PLAYER, CAPTAIN, COACH
jerseyNumberinteger | nullminimum: 0maximum: 999No

Event

Event
NameTypeRequiredDescription
idstringYesEquals the webhook-id header of the matching delivery.
typestringYesValues: league.created, season.created, season.closed, season.reopened, season.abandoned, season.renewed, category.created, category.updated, registration.submitted, registration.withdrawn, registration.reinstated, schedule.published, match.rescheduled, match.completed, match.awarded, match.corrected, match.cancelled, match.voided, series.opened, series.updated
createdstringformat: date-timeYes
organizationstringYes
dataobjectYesTHIN PAYLOAD, always: an object, its identifier, its revision. Never a name, an address or a date of birth — a webhook URL is an exit point to a third party, often logged in clear in ITS logs. The consumer reads the current state again; it does not replay a six-hour-old state.
data.objectstringNo
data.idstringNo
data.revisioninteger | nullNo
data.previousobjectNo
data.scheduledobjectNomatch.rescheduled ONLY — the new slot, while previous carries the one it replaces: resourceId, startsAt, endsAt. It is the only event that carries two values, because it is the only fact a consumer cannot rebuild without having kept its own state. It is emitted when the schedule is PUBLISHED, once per match whose published slot changed: a draft move was shown to no one and is therefore never announced.

Error codes

An error’s code field takes one of these values. They do not change: build your logic on them.

  • UNAUTHORIZED
  • FORBIDDEN
  • NOT_FOUND
  • CONFLICT
  • INVALID_INPUT
  • IDEMPOTENCY_KEY_REUSED
  • IDEMPOTENT_REQUEST_IN_FLIGHT
  • IDEMPOTENCY_OUTCOME_UNKNOWN
  • ENTITLEMENT_DENIED
  • ENTITLEMENT_UNAVAILABLE
  • HOLDER_UNINITIALIZED
  • BODY_TOO_LARGE
  • WEBHOOK_VAULT_UNAVAILABLE
  • ROTATION_EN_COURS
  • LEAGUES_UNAVAILABLE
  • RATE_LIMITED