Pitch Connect API
Base URL: https://pitch-connect.space
Club and tournament data are read-enabled. Match scores, goals, highlights, and markers are write-enabled where the selected resource supports them.
🤖 AI Agents & LLMs: Building with AI coding assistants (Cursor, Antigravity, Claude, GPT, AutoGPT)? We provide dedicated machine-readable AI docs and specifications:
- LLM Index:/llms.txt(or/.well-known/llms.txt)
- Full LLM Spec & Tool Definitions:/llms-full.txt
- AI Agent Integration Guide:/api/docs/ai
- OpenAPI Specifications:/api/openapi.json(JSON) ·/api/openapi.yaml(YAML)
Overview
The Pitch Connect API lets third-party apps read club squad data and fixtures, import tournament matches, write club game scores and goal scorers, and record granular match event markers for both pickup games and tournament matches.
Supported token types
| Resource | Token type | Description | Where to get it |
|---|---|---|---|
| Account | Connected app token | One token per connected user/app. It can access every club and tournament that user currently has permission to manage. Use it with explicit resource URLs such as /api/v1/clubs/:slug and /api/v1/tournaments/:slug. |
OAuth flow below |
| Club | Static key | A single shared key for the club. Good for scripts and direct integrations. | Club → Integrations page |
| Tournament | Static key | A shared tournament key for direct integrations. | Tournament integration flow |
| Club / Tournament | Legacy per-resource app token | Existing app connections created before the unified flow. Still supported by the generic /api/v1/club and /api/v1/tournament style endpoints. |
Existing integrations |
OAuth-style App Connection
Use this when you're building an app and want your users to connect a Pitch Connect account without manually copying API keys.
Full flow
1. Your app → redirect user to:
GET https://pitch-connect.space/api/auth/connect
?app_name=MyApp
&redirect_uri=https://myapp.com/callback
2. User logs in to Pitch Connect (if not already signed in)
A focused consent screen is shown — no app nav or footer.
3. User sees your app name and the clubs/tournaments they can access.
They confirm the connection once.
4. Pitch Connect → redirects user back to your app with a
fresh connected-app token unique to this user/app connection:
GET https://myapp.com/callback
?api_key=a1b2c3d4...
&resource_type=account
&user_name=Alex+Johnson
&club_count=2
&tournament_count=3
Step 1 — Redirect the user
GET https://pitch-connect.space/api/auth/connect?app_name=MyApp&redirect_uri=https://myapp.com/callback
| Parameter | Required | Description |
|---|---|---|
app_name |
Yes | Your app's name, shown on the consent screen |
redirect_uri |
Yes | Where to send the user after connecting. Must be https:// in production (http://localhost allowed in development). Cannot point to pitch-connect.space. |
Step 2 — Handle the callback
After the user confirms the connection, Pitch Connect redirects to your redirect_uri with:
| Parameter | Type | Description |
|---|---|---|
api_key |
string | A fresh connected-app token. Store securely. |
resource_type |
string | Always account for the unified app flow |
user_name |
string | Human-readable Pitch Connect user name |
club_count |
integer | Number of clubs this token can currently access |
tournament_count |
integer | Number of tournaments this token can currently access |
Callback example
https://myapp.com/callback?api_key=a1b2c3d4...&resource_type=account&user_name=Alex+Johnson&club_count=2&tournament_count=3
Step 3 — Discover accessible resources
Once you have the token, call:
curl https://pitch-connect.space/api/v1/resources \
-H "Authorization: Bearer <your_connected_app_token>"
This returns the clubs and tournaments the token can use with explicit resource URLs.
Who can connect
| Resource | Role | Can connect? |
|---|---|---|
| Club | Club owner | Yes |
| Club | Club moderator | Yes |
| Club | Club member only | No |
| Tournament | Tournament organizer | Yes |
| Tournament | Host club moderator | Yes |
| Tournament | Participant only | No |
| Any | Pitch Connect admin | Yes |
Re-connecting
Each user/app connection has one shared connected-app token. Reconnecting returns the existing active token for that user/app pair.
Manual Key Generation
For scripts and direct integrations.
Club static key
- Go to your club page on Pitch Connect
- Click Manage → next to the API & Integrations summary in the sidebar
- Navigate to
/clubs/{your-club-slug}/integrations - Under Static API Key, click Generate Static Key
- Copy the key — treat it like a password
From the same page: Regenerate (rotates key, old one stops working immediately) or Revoke (removes access entirely).
Tournament static key
Tournament static keys are also supported by tournament endpoints and tournament match marker endpoints. They are passed the same way:
Authorization: Bearer <your_tournament_api_key>
Managing Integrations
The Integrations page (/clubs/{your-club-slug}/integrations) is the central hub for API access:
- Connected Apps — lists every OAuth-connected app with its name, connection date, last-used time, and a Disconnect button to revoke that token
- Static API Key — generate, regenerate, or revoke the shared key
Making API Requests
All endpoints are under /api/v1/ and require a token as a Bearer header.
Authorization: Bearer <your_api_key>
Recommended flow with a connected-app token:
- Call
GET /api/v1/resources - Pick a club slug or tournament slug from that response
- Call explicit resource endpoints such as:
GET /api/v1/clubs/:club_slugGET /api/v1/tournaments/:tournament_slugPOST /api/v1/clubs/:club_slug/games/:slug/markerPOST /api/v1/tournaments/:tournament_slug/matches/:match_id/markers
Legacy generic endpoints such as GET /api/v1/club and GET /api/v1/tournament still work with static keys and older per-resource app tokens.
Endpoints
GET /api/v1/resources
Lists all clubs and tournaments accessible to a connected-app token.
curl https://pitch-connect.space/api/v1/resources \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"user": {
"id": 7,
"name": "Alex Johnson",
"email": "alex@example.com"
},
"clubs": [
{
"id": 12,
"name": "Westminster FC",
"slug": "westminster-fc",
"role": "owner"
}
],
"tournaments": [
{
"id": 18,
"name": "GMC Cup",
"slug": "gmc-cup",
"status": "group_stage",
"role": "organizer"
}
]
}
GET /api/v1/tournaments/:tournament_slug
Legacy alias for tournament static keys and older resource tokens: GET /api/v1/tournament
Returns tournament metadata, accepted teams, the tournament player list, and the current tournament match list.
curl https://pitch-connect.space/api/v1/tournaments/gmc-cup \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"id": 18,
"name": "GMC Cup",
"slug": "gmc-cup",
"status": "group_stage",
"format": "tournament",
"tournament_type": "inter_club",
"description": "Summer 7v7 tournament.",
"start_date": "2026-07-20",
"end_date": "2026-08-03",
"location": "Denver, CO",
"host_club": {
"id": 7,
"name": "GMC FC",
"slug": "gmc-fc"
},
"teams": [
{
"id": 101,
"name": "Home FC",
"club_id": 12,
"club_slug": "home-fc",
"group_name": "A",
"points": 3,
"matches_played": 1,
"wins": 1,
"draws": 0,
"losses": 0,
"goals_for": 2,
"goals_against": 1,
"goal_difference": 1
}
],
"players": [
{
"id": 42,
"slug": "alex-rivera",
"name": "Alex Rivera",
"avatar_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"jersey_number": 10,
"team_position": "midfielder",
"preferred_position": "midfielder",
"secondary_positions": ["forward"],
"skill_level": "intermediate",
"captain": true,
"team_id": 101,
"team_name": "Home FC",
"club_id": 12,
"club_slug": "home-fc",
"profile_url": "https://pitch-connect.space/players/alex-rivera"
}
],
"upcoming_match_count": 2,
"completed_match_count": 1,
"matches": [
{
"id": 42,
"stage": "group",
"group_name": "A",
"round": 1,
"knockout_round": null,
"knockout_round_label": null,
"status": "completed",
"scheduled_at": "2026-07-20T18:00:00Z",
"played_at": "2026-07-20T18:00:00Z",
"started_at": "2026-07-20T18:01:00Z",
"ended_at": "2026-07-20T18:52:00Z",
"period": "full_time",
"field": "Field 1",
"pitch": "North",
"home_team": {
"id": 12,
"slug": "home-fc",
"name": "Home FC",
"placeholder": null
},
"away_team": {
"id": 15,
"slug": "away-fc",
"name": "Away FC",
"placeholder": null
},
"lineups": {
"home": {
"starters": [
{
"id": 45,
"slug": "alex-rivera",
"name": "Alex Rivera",
"avatar_url": "https://pitch-connect.space/rails/active_storage/blobs/...",
"jersey_number": 10,
"captain": true,
"position": "CAM",
"pitch_x": 50.0,
"pitch_y": 70.0
}
],
"substitutes": []
},
"away": {
"starters": [],
"substitutes": []
}
},
"score": {
"home": 2,
"away": 1,
"home_penalties": null,
"away_penalties": null
},
"winner_club_id": 12,
"goals": [
{
"team": "home",
"scorer": "Alex Rivera",
"minute": "9"
}
],
"cards": [],
"commentary": [],
"stream_url": null,
"notes": null
}
],
"url": "https://pitch-connect.space/tournaments/gmc-cup"
}
GET /api/v1/tournaments/:tournament_slug/players
Legacy alias for tournament static keys and older resource tokens: GET /api/v1/tournament/players
Returns tournament players with team affiliation and profile image URLs.
For intra-club tournaments, this includes registration metadata such as jersey preferences and waiver acceptance timestamps.
curl https://pitch-connect.space/api/v1/tournaments/gmc-cup/players \
-H "Authorization: Bearer <your_connected_app_token>"
Inter-club response
json
{
"tournament": {
"id": 18,
"name": "GMC Cup",
"slug": "gmc-cup",
"status": "group_stage",
"format": "tournament",
"tournament_type": "inter_club"
},
"players": [
{
"id": 42,
"slug": "alex-rivera",
"name": "Alex Rivera",
"avatar_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"jersey_number": 10,
"team_position": "midfielder",
"preferred_position": "midfielder",
"secondary_positions": ["forward"],
"skill_level": "intermediate",
"captain": true,
"team_id": 101,
"team_name": "Home FC",
"club_id": 12,
"club_slug": "home-fc",
"profile_url": "https://pitch-connect.space/players/alex-rivera"
}
]
}
Intra-club response
json
{
"tournament": {
"id": 25,
"name": "Dashain Cup",
"slug": "dashain-cup",
"status": "registration",
"format": "tournament",
"tournament_type": "intra_club"
},
"players": [
{
"id": 88,
"slug": "nilo-makai",
"name": "Nilo Makai",
"avatar_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"preferred_position": "forward",
"secondary_positions": ["midfielder"],
"skill_level": "beginner",
"registration_status": "registered",
"jersey_number": 5,
"team_position": "forward",
"jersey_size": "M",
"jersey_name": "Nilo",
"jersey_number_preferences": [5, 3],
"waiver_accepted_at": "2026-07-15T06:10:00Z",
"team_id": 201,
"team_name": "Dashain Cup - Team A",
"club_id": 9,
"club_slug": "himalayan-football-club-hfc",
"profile_url": "https://pitch-connect.space/players/nilo-makai"
}
]
}
GET /api/v1/tournaments/:tournament_slug/matches
Legacy alias for tournament static keys and older resource tokens: GET /api/v1/tournament/matches
Returns the tournament match list, suitable for "import all matches" workflows in third-party apps.
curl https://pitch-connect.space/api/v1/tournaments/gmc-cup/matches \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"tournament": {
"id": 18,
"name": "GMC Cup",
"slug": "gmc-cup",
"status": "group_stage",
"format": "tournament",
"tournament_type": "inter_club"
},
"matches": [
{
"id": 42,
"stage": "group",
"group_name": "A",
"round": 1,
"knockout_round": null,
"knockout_round_label": null,
"status": "completed",
"scheduled_at": "2026-07-20T18:00:00Z",
"played_at": "2026-07-20T18:00:00Z",
"started_at": "2026-07-20T18:01:00Z",
"ended_at": "2026-07-20T18:52:00Z",
"period": "full_time",
"field": "Field 1",
"pitch": "North",
"home_team": {
"id": 12,
"slug": "home-fc",
"name": "Home FC",
"placeholder": null
},
"away_team": {
"id": 15,
"slug": "away-fc",
"name": "Away FC",
"placeholder": null
},
"lineups": {
"home": {
"starters": [
{
"id": 45,
"slug": "alex-rivera",
"name": "Alex Rivera",
"avatar_url": "https://pitch-connect.space/rails/active_storage/blobs/...",
"jersey_number": 10,
"captain": true,
"position": "CAM",
"pitch_x": 50.0,
"pitch_y": 70.0
}
],
"substitutes": []
},
"away": {
"starters": [],
"substitutes": []
}
},
"score": {
"home": 2,
"away": 1,
"home_penalties": null,
"away_penalties": null
},
"winner_club_id": 12,
"goals": [
{
"team": "home",
"scorer": "Alex Rivera",
"minute": "9"
}
],
"cards": [],
"commentary": [],
"stream_url": null,
"notes": null
}
]
}
Lineups object
Each tournament match contains a lineups object with home and away keys. Each side contains starters and substitutes arrays of player objects:
| Field | Type | Description |
|---|---|---|
id |
integer | User ID |
slug |
string | Player slug |
name |
string | Full name of player |
avatar_url |
string \ | null |
jersey_number |
integer \ | null |
captain |
boolean | true if player is team captain |
position |
string \ | null |
pitch_x |
float \ | null |
pitch_y |
float \ | null |
GET /api/v1/clubs/:club_slug
Legacy alias for club static keys and older resource tokens: GET /api/v1/club
Returns the full club profile: details, all active players, and next 20 upcoming games in one call.
curl https://pitch-connect.space/api/v1/clubs/westminster-fc \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"id": 12,
"name": "Westminster FC",
"slug": "westminster-fc",
"city": "London",
"location": "Westminster, London",
"description": "Sunday league side, all levels welcome.",
"member_count": 18,
"is_private": false,
"created_at": "2024-09-01T10:00:00Z",
"badge_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"cover_photo_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"players": [ ... ],
"upcoming_games": [ ... ]
}
| Field | Type | Notes |
|---|---|---|
badge_url |
string \ | null |
cover_photo_url |
string \ | null |
GET /api/v1/clubs/:club_slug/players
Legacy alias for club static keys and older resource tokens: GET /api/v1/club/players
Active squad members with jersey numbers, positions, and skill levels.
Jersey numbers and club positions are set by club staff in the app (Members page) and read here.
curl https://pitch-connect.space/api/v1/clubs/westminster-fc/players \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"club": "Westminster FC",
"players": [
{
"id": 42,
"slug": "alex-johnson",
"name": "Alex Johnson",
"avatar_url": "https://pitch-connect.space/rails/active_storage/...jpg",
"jersey_number": 10,
"club_position": "midfielder",
"effective_position": "midfielder",
"preferred_position": "forward",
"secondary_positions": ["defender"],
"skill_level": "intermediate",
"role": "Owner",
"joined_at": "2024-09-01T10:00:00Z",
"profile_url": "https://pitch-connect.space/players/alex-johnson"
}
]
}
Player fields
| Field | Type | Notes |
|---|---|---|
id |
integer | User ID — use this to match players in game team lists |
slug |
string | URL-friendly identifier |
name |
string | Full display name |
jersey_number |
integer | Squad number (1–99). Omitted if not assigned |
club_position |
string | Position assigned by the club: goalkeeper · defender · midfielder · forward. Omitted if not set |
effective_position |
string | club_position if set, otherwise preferred_position. Use this for lineup and team formation |
preferred_position |
string | Player's own preferred position |
secondary_positions |
string[] | Other positions the player covers. May be empty |
skill_level |
string | beginner · intermediate · advanced |
role |
string | Owner · Moderator · Member |
joined_at |
ISO 8601 | UTC timestamp when they joined the club |
profile_url |
string | Link to their Pitch Connect profile |
GET /api/v1/clubs/:clubslug/upcominggames
Legacy alias for club static keys and older resource tokens: GET /api/v1/club/upcoming_games
Next 20 games ordered by kick-off time with full team breakdowns and match pairings.
curl https://pitch-connect.space/api/v1/clubs/westminster-fc/upcoming_games \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"club": "Westminster FC",
"games": [
{
"id": 301,
"slug": "westminster-sunday-kickabout",
"title": "Westminster Sunday Kickabout",
"matchup": "Reds vs Blues · Greens vs Yellows",
"date_time": "2026-06-01T10:00:00Z",
"timezone": "London",
"location": "Battersea Park, London",
"field": "Pitch 3",
"pitch": "5-a-side",
"description": "Friendly match, bring water.",
"notes": "Gates open 15 mins before kick-off.",
"max_players": 10,
"players_joined": 7,
"spots_left": 3,
"skill_level": "intermediate",
"cost_per_player": 5.0,
"is_public": true,
"organizer": { "id": 1, "name": "Alex Johnson" },
"teams": [
{
"number": 1,
"name": "Reds",
"players": [
{
"id": 42,
"name": "Alex Johnson",
"is_guest": false,
"player_type": "user",
"is_captain": true,
"is_substitute": false,
"pitch_role": "goalkeeper",
"checked_in": true
}
],
"maybe_players": [
{
"id": 43,
"name": "Sam Lee",
"is_guest": false,
"player_type": "user",
"is_captain": false,
"is_substitute": false,
"pitch_role": null,
"checked_in": false
}
]
}
],
"matchups": [
{ "match_id": "301_0", "match_index": 0, "home_team": 1, "home_name": "Reds", "away_team": 2, "away_name": "Blues" },
{ "match_id": "301_1", "match_index": 1, "home_team": 3, "home_name": "Greens", "away_team": 4, "away_name": "Yellows" }
],
"unassigned_players": [],
"maybe_unassigned_players": [],
"score": "Reds 2 · Blues 1",
"url": "https://pitch-connect.space/games/westminster-sunday-kickabout"
}
]
}
Game fields
| Field | Type | Notes |
|---|---|---|
matchup |
string | All pairings in one string: "Reds vs Blues · Greens vs Yellows". Bye shown as "Team X (bye)" |
date_time |
ISO 8601 | Kick-off time in UTC |
timezone |
string | Timezone the game was scheduled in |
location |
string \ | null |
field |
string \ | null |
pitch |
string \ | null |
description |
string \ | null |
notes |
string \ | null |
max_players |
integer | Total player capacity |
players_joined |
integer | Confirmed players |
spots_left |
integer | max_players − players_joined |
skill_level |
string \ | null |
cost_per_player |
number \ | null |
organizer |
object | { id, name } |
teams |
array | See team object below |
matchups |
array | [{ home_team, home_name, away_team, away_name }]. away_* are null for a bye |
unassigned_players |
array | Confirmed players not yet on a team |
maybe_unassigned_players |
array | Maybe RSVPs not yet on a team |
score |
string \ | null |
url |
string | Link to the game on Pitch Connect |
Team object
| Field | Type | Notes |
|---|---|---|
number |
integer | Team number |
name |
string | Custom name or "Team N" |
players |
array | Confirmed players on this team |
maybe_players |
array | Maybe RSVPs on this team |
Player-in-game object
| Field | Type | Notes |
|---|---|---|
id |
integer \ | null |
name |
string | Display name |
is_guest |
boolean | true if not a registered user |
player_type |
string | user for registered players, junior for saved junior members, or guest for unnamed plus-ones |
is_captain |
boolean | Team captain |
is_substitute |
boolean | On the bench |
pitch_role |
string \ | null |
checked_in |
boolean | Whether they checked in |
GET /api/v1/clubs/:clubslug/pastgames {#get-api-v1-clubs-clubslug-pastgames}
Legacy alias for club static keys and older resource tokens: GET /api/v1/club/past_games
Recently completed games in reverse chronological order (newest first). Each game includes the full score breakdown, individual goal events, and aggregated scorer totals.
Each game object also includes the same RSVP breakdown fields as upcoming games: teams[].maybe_players and maybe_unassigned_players.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 20 |
Games per page (max 50) |
page |
integer | 1 |
Page number |
curl "https://pitch-connect.space/api/v1/clubs/westminster-fc/past_games?limit=10&page=1" \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"club": "Westminster FC",
"page": 1,
"limit": 10,
"games": [
{
"id": 300,
"slug": "westminster-sunday-kickabout-2026-06-15",
"title": "Westminster Sunday Kickabout",
"date_time": "2026-06-15T10:00:00Z",
"location": "Battersea Park, London",
"score": "Reds 3 · Blues 1",
"result": {
"scores": [
{ "team": "Reds", "score": 3 },
{ "team": "Blues", "score": 1 }
],
"scorers": [
{ "name": "Alex Johnson", "goals": 2 },
{ "name": "Sam Lee", "goals": 1 }
],
"goals": [
{ "scorer": "Alex Johnson", "assister": "Sam Lee", "team": "1", "own_goal": false },
{ "scorer": "Sam Lee", "assister": null, "team": "1", "own_goal": false },
{ "scorer": "Alex Johnson", "assister": null, "team": "1", "own_goal": false },
{ "scorer": "Chris Evans", "assister": null, "team": "2", "own_goal": false }
]
}
}
]
}
result object
| Field | Type | Notes |
|---|---|---|
scores |
array | [{ team, score }] — one entry per team |
scorers |
array | Aggregated totals per player, sorted by goals desc |
goals |
array | Individual goal events in recorded order |
goals[].own_goal |
boolean | true if the goal was credited as an own goal |
All other fields are identical to the upcoming games response, including the RSVP fields.
PATCH /api/v1/clubs/:club_slug/games/:slug/scores
Legacy alias for club static keys and older resource tokens: PATCH /api/v1/games/:slug/scores
Update the score for each team. Only works for games belonging to the authenticated club.
curl -X PATCH https://pitch-connect.space/api/v1/clubs/westminster-fc/games/thursday-kickabout/scores \
-H "Authorization: Bearer <your_connected_app_token>" \
-H "Content-Type: application/json" \
-d '{ "scores": { "1": 2, "2": 1 } }'
| Field | Type | Description |
|---|---|---|
scores |
object | Map of team number (string key) to score (integer). E.g. { "1": 2, "2": 0, "3": 1 } |
Response
json
{
"message": "Scores updated.",
"game": {
"id": 301,
"slug": "thursday-kickabout",
"title": "Thursday Kickabout",
"team_scores": { "1": 2, "2": 1 },
"score": "Reds 2 · Blues 1",
"goals": [ ... ]
}
}
PATCH /api/v1/clubs/:club_slug/games/:slug/goals
Legacy alias for club static keys and older resource tokens: PATCH /api/v1/games/:slug/goals
Replace the full goal list. Sends the complete array — overwrites all existing goals.
curl -X PATCH https://pitch-connect.space/api/v1/clubs/westminster-fc/games/thursday-kickabout/goals \
-H "Authorization: Bearer <your_connected_app_token>" \
-H "Content-Type: application/json" \
-d '{
"goals": [
{
"team": "1",
"scorer": "Alex Johnson",
"user_id": 42,
"minute": "23",
"penalty": false,
"own_goal": false,
"assister": "Sam Smith",
"assister_user_id": 15
}
]
}'
Goal object
| Field | Required | Notes |
|---|---|---|
team |
Yes | Team number as string: "1", "2", etc. |
scorer |
Yes | Scorer display name |
user_id |
No | Scorer's user ID from /players. Omit for guests |
minute |
No | Minute of goal, e.g. "45" |
penalty |
No | true if penalty |
own_goal |
No | true if own goal |
assister |
No | Assister display name |
assister_user_id |
No | Assister user ID from /players |
Note: Scores and goals are independent. Call both endpoints to keep them in sync, or derive scores by counting goals in your app.
POST /api/v1/clubs/:club_slug/games/:slug/marker
Legacy alias for club static keys and older resource tokens: POST /api/v1/games/:slug/marker
Record a single match event marker. Idempotent — sending the same id again updates the existing marker rather than creating a duplicate.
The game must belong to the club identified by your API token.
curl -X POST https://pitch-connect.space/api/v1/clubs/westminster-fc/games/thursday-kickabout/marker \
-H "Authorization: Bearer <your_connected_app_token>" \
-H "Content-Type: application/json" \
-d '{
"id": "e305e94b-4b10-4bc3-be9d-472d2fb2d2e1",
"timestamp": "2026-06-02T14:15:22Z",
"type": "Save",
"primary_player_pc_id": "8432",
"primary_player_type": "junior",
"secondary_player_pc_id": "9021",
"secondary_player_type": "user",
"label": "Tipped over crossbar"
}'
Request body
| Field | Required | Description |
|---|---|---|
id |
Yes | Client-generated UUID — prevents duplicate markers (upsert key) |
match_id |
Yes | The match_id from the matchup in /club/upcoming_games. Format: {game_id}_{match_index} e.g. 301_0. Required when the game has multiple match pairings. |
timestamp |
Yes | ISO-8601 datetime of the event |
type |
Yes | Event type — see allowed values below |
primary_player_pc_id |
No | Player id from the game roster for the primary player (scorer, shooter, outgoing sub…) |
primary_player_type |
No | The roster player's player_type: user or junior. Recommended for juniors to prevent numeric ID ambiguity. |
secondary_player_pc_id |
No | Player id from the game roster for the secondary player (assister, chance creator, incoming sub…) |
secondary_player_type |
No | The roster player's player_type: user or junior. Recommended for juniors to prevent numeric ID ambiguity. |
label |
No | Optional free-text note |
For backward compatibility, omitted player types are inferred from the game's participants. Integrations should send the corresponding player_type returned by the game roster, especially for juniors, because user IDs and junior IDs use separate numeric namespaces.
Allowed type values
| Category | Types | Effect |
|---|---|---|
| Match lifecycle | Start |
Begins the match / half. Starts the match clock. |
Break |
Pauses play (half-time, injury time). Pauses clock. | |
Resume |
Restarts play after a break. Resumes clock. | |
End |
Ends the match or half. | |
PenaltyShootout |
Marks the start of a penalty shootout period. | |
ResetMatch |
Clears all scores, goals, cards, and markers. If match_id is given resets only that pairing; otherwise resets the full game. |
|
Keeper |
Establishes the goalkeeper for each team. primary_player_pc_id = home keeper, secondary_player_pc_id = away keeper. Send before match start and again if keeper changes. GK is referenced by subsequent Save markers. |
|
| Goals | Goal |
Regular goal. Increments scorer's team score. Own goal detected via label containing "own goal" or "og". |
PenaltyScored |
Penalty kick scored. Increments scorer's team score. | |
PenaltyMissed |
Penalty kick missed. No score change. | |
| Shots | Shot |
Shot off target (missed the frame). Counted in Shots off Target stats. |
Miss |
Shot that missed the goal entirely (completely off-frame). Counted as Missed Chances. | |
Save |
Goalkeeper save. primary_player_pc_id = shooter (not GK), secondary_player_pc_id = chance creator. GK is determined from the last Keeper marker for the match. Counted in Shots on Target for the shooter's team and GK Saves for the defending team. |
|
ChanceCreated |
Key pass or through ball that creates a shooting opportunity. primary_player_pc_id = chance creator. Counted in Chances Created stats. |
|
| Discipline | YellowCard |
Yellow card issued to primary_player_pc_id. |
RedCard |
Red card issued to primary_player_pc_id. |
|
Foul |
Foul committed. Counted in Fouls stats. | |
| Defensive | Defense |
Defensive contribution (clearance, block, interception). primary_player_pc_id = defender. Counted in Defensive Contributions stats. |
| Tactical | Substitution |
Player substitution. primary_player_pc_id = outgoing, secondary_player_pc_id = incoming. No score change. |
Penalty |
Structural foul leading to a penalty (different from PenaltyScored). Records fouler/fouled. |
|
| Other | Event |
Free-form event (e.g. injury stoppage, VAR review). Stored for the timeline only. |
Player field mapping by type
| Type | primary_player_pc_id |
secondary_player_pc_id |
|---|---|---|
Goal |
Scorer | Assister (optional) |
PenaltyScored |
Penalty taker | — |
PenaltyMissed |
GK (if saved) / Taker (if missed) | Taker (if saved) / — |
Shot / Miss |
Shooter | Chance creator (optional) |
Save |
Saving goalkeeper | — |
YellowCard / RedCard |
Booked player | — |
Foul |
Foul committer | Fouled player |
Penalty |
Fouled player | Foul committer |
Substitution |
Outgoing player | Incoming player |
PenaltyShootout / Start / Break / Resume / End / Event / ResetMatch |
— | — |
How shots are counted in stats
| Stat | Counted from |
|---|---|
| Shots on Target | Goal + PenaltyScored (scorer's team) + Save (shooter's team — primary_player is the shooter) |
| Shots off Target | Shot — shot that missed the frame or was blocked (shooter's team) |
| Missed Chances | Miss — completely off-target attempt (shooter's team) |
| Chances Created | ChanceCreated (creator's team) |
| GK Saves | Save — attributed to the defending team (opposite of shooter's team) |
| Defensive Contributions | Defense (defending player's team) |
| Fouls | Foul (fouling player's team) |
Response
json
{ "status": "success", "marker_id": "e305e94b-4b10-4bc3-be9d-472d2fb2d2e1" }
Returns 201 Created for new markers, 200 OK for updates.
GET /api/v1/clubs/:club_slug/games/:slug/markers
Legacy alias for club static keys and older resource tokens: GET /api/v1/games/:slug/markers
Returns all markers for a game ordered by timestamp.
curl https://pitch-connect.space/api/v1/clubs/westminster-fc/games/thursday-kickabout/markers \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"game": { "id": 301, "slug": "thursday-kickabout" },
"game_mode": false,
"markers": [
{
"id": "e305e94b-...",
"timestamp": "2026-06-02T14:15:22Z",
"type": "Save",
"primary_player_pc_id": "8432",
"primary_player_type": "junior",
"secondary_player_pc_id": "9021",
"secondary_player_type": "user",
"label": "Tipped over crossbar",
"primary_player": { "id": 8432, "name": "Jamie Junior", "type": "junior" },
"secondary_player": { "id": 9021, "name": "Sam Smith", "type": "user" }
}
]
}
game_mode: true means the game is in Game Mode On — only Start, Goal, YellowCard, RedCard, Substitution, Break, Resume, PenaltyScored, PenaltyMissed, PenaltyShootout, ResetMatch will be present. The base Penalty marker (with fouler/fouled details) is not transmitted in game mode.
DELETE /api/v1/clubs/:club_slug/games/:slug/markers/:id
Deletes a game marker by its client id. If the deleted marker is a Goal or PenaltyScored marker, the associated goal is automatically removed and the team score is reverted.
curl -X DELETE https://pitch-connect.space/api/v1/clubs/westminster-fc/games/thursday-kickabout/markers/marker-goal-1 \
-H "Authorization: Bearer <your_connected_app_token>"
Response
json
{
"status": "success",
"message": "Marker deleted and stats updated."
}
POST /api/v1/tournaments/:tournamentslug/matches/:matchid/markers
Records a tournament match marker. This endpoint supports live tournament control markers such as:
StartBreakHalfTimeResumeExtraTime1ExtraTime2EndFullTimeGoalPenaltyScoredYellowCardRedCard- commentary-style event markers
curl -X POST https://pitch-connect.space/api/v1/tournaments/gmc-cup/matches/42/markers \
-H "Authorization: Bearer <your_tournament_api_key>" \
-H "Content-Type: application/json" \
-d '{
"id": "marker-123",
"timestamp": "2026-07-14T18:45:00Z",
"type": "Goal",
"team": "home",
"scorer": "Alex",
"minute": "12"
}'
Tournament tokens can use this endpoint directly. Club tokens can also use it if they are authorized for the tournament match.
Response
json
{
"status": "success",
"marker_id": "marker-123"
}
DELETE /api/v1/tournaments/:tournamentslug/matches/:matchid/markers/:id
Deletes a tournament match marker by its client id. If the deleted marker is a Goal or PenaltyScored marker, the associated goal is automatically removed and match scores are recalculated and reverted.
curl -X DELETE https://pitch-connect.space/api/v1/tournaments/gmc-cup/matches/42/markers/marker-goal-1 \
-H "Authorization: Bearer <your_tournament_api_key>"
Response
json
{
"status": "success",
"message": "Marker deleted and match score updated.",
"match": {
"id": 42,
"status": "live",
"home_team_name": "Home FC",
"away_team_name": "Away FC"
}
}
GET /api/v1/tournaments/:tournamentslug/matches/:matchid/markers
Lists tournament match markers in chronological order.
curl https://pitch-connect.space/api/v1/tournaments/gmc-cup/matches/42/markers \
-H "Authorization: Bearer <your_tournament_api_key>"
Response
json
{
"tournament": {
"id": 18,
"slug": "gmc-cup",
"name": "GMC Cup"
},
"match": {
"id": 42,
"status": "live",
"home_team_name": "Home FC",
"away_team_name": "Away FC"
},
"markers": [
{
"id": "marker-start-1",
"timestamp": "2026-07-20T18:01:00Z",
"type": "Start",
"primary_player_pc_id": null,
"secondary_player_pc_id": null,
"label": null,
"primary_player": null,
"secondary_player": null
},
{
"id": "marker-goal-1",
"timestamp": "2026-07-20T18:10:00Z",
"type": "Goal",
"primary_player_pc_id": "42",
"secondary_player_pc_id": null,
"label": "Home FC",
"primary_player": {
"id": 42,
"name": "Alex Rivera"
},
"secondary_player": null
}
]
}
Error Responses
| HTTP Status | Meaning |
|---|---|
401 Unauthorized |
Missing or invalid token |
403 Forbidden |
Valid token, but wrong resource type or not allowed for that club/tournament |
404 Not Found |
Resource not found or doesn't belong to the connected resource |
422 Unprocessable |
Invalid request body |
401 example
json
{
"error": "Invalid API token.",
"hint": "Use a club API key, connected club app token, or tournament API key."
}
Notes
- All timestamps are UTC ISO 8601
GET /api/v1/clubreturns players + games in one call — use the focused endpoints if you only need one- Only active members appear in the players list — pending and former members are excluded
- Jersey numbers and club positions are set in the app by club staff (Members page) and read via the API
- Use
effective_positionfor lineup/formation — it returnsclub_positionif set, otherwise falls back topreferred_position - Games are capped at 20, ordered by kick-off time ascending
- Updating goals does not auto-update
team_scores— call both endpoints or count goals in your app - Event markers (
POST /game/:slug/marker) are idempotent — send the same clientidto update without creating a duplicate - Deleting a marker (
DELETE /markers/:id) automatically removes associated goal entries and reverts team scores & match stats forGoalorPenaltyScoredmarkers - When
game_modeistrue, only simplified types are sent:Start,Goal,YellowCard,RedCard,Substitution,Break,Resume,PenaltyScored,PenaltyMissed,PenaltyShootout,ResetMatch— the structuralPenaltymarker is not transmitted in game mode primary_player_pc_id/secondary_player_pc_idare the player'sidfrom the/playersendpoint- Write game endpoints only accept games belonging to the club identified by the token
- Tournament endpoints require a tournament token, except tournament match marker writes which also accept authorized club tokens
- Per-app tokens can be revoked individually without affecting other connections