pitch-connect.space / api/docs

API Reference

REST API · All responses are JSON · Auth via Bearer token

Pitch Connect API

Base URL: https://pitch-connect.space

Club data (players, games) is read-only. Match scores, goals, and event markers are write-enabled.


Overview

The Pitch Connect API lets third-party apps read club squad data and upcoming fixtures, write match scores and goal scorers, and record granular match event markers (goals, shots, saves, cards, substitutions, and more) for video analysis and live match tracking.

Two token types work interchangeably:

Type Description Where to get it
Per-app token Generated when a user connects your app via OAuth. Each connection gets its own independent token — revoke one without affecting others. OAuth flow below
Static key A single shared key for the club. Good for scripts and direct integrations. Club → Integrations page

OAuth-style Club Connection

Use this when you're building an app and want your users to connect their Pitch Connect club 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 their eligible clubs.
   They click the club they want to connect.

4. Pitch Connect  →  redirects user back to your app with a
   fresh per-app token unique to this connection:
                GET https://myapp.com/callback
                    ?api_key=a1b2c3d4...
                    &club_name=Westminster+FC
                    &club_slug=westminster-fc

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 selects a club, Pitch Connect redirects to your redirect_uri with:

Parameter Type Description
api_key string A fresh per-app token — unique to this connection. Store securely.
club_name string Human-readable club name
club_slug string URL slug for the club

Example:

https://myapp.com/callback?api_key=a1b2c3d4...&club_name=Westminster+FC&club_slug=westminster-fc

Who can connect a club

Role Can connect?
Club owner Yes
Club moderator Yes
Club member only No
Pitch Connect admin Yes (any club)

Re-connecting

Each OAuth connection creates a new per-app token. Previous tokens from earlier connections continue working until revoked. This lets a user reconnect (e.g. after revoking) without breaking other integrations.

Revoking a connection

Club owners can see all connected apps and revoke individual tokens from:

https://pitch-connect.space/clubs/{your-club-slug}/integrations

Revoking one app does not affect other connected apps or the static key.


Manual Key Generation

For scripts and direct integrations — a single shared key for the club.

  1. Go to your club page on Pitch Connect
  2. Click Manage → next to the API & Integrations summary in the sidebar — or navigate directly to /clubs/{your-club-slug}/integrations
  3. Under Static API Key, click Generate Static Key
  4. 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).


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>

The token identifies which club's data is being accessed — no slug needed in the URL.


Endpoints

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/club \
  -H "Authorization: Bearer <your_api_key>"

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/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/club/players \
  -H "Authorization: Bearer <your_api_key>"

Response
json
{
"club": "Westminster FC",
"players": [
{
"id": 42,
"slug": "alex-johnson",
"name": "Alex Johnson",
"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/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/club/upcoming_games \
  -H "Authorization: Bearer <your_api_key>"

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,
"is_captain": true,
"is_substitute": false,
"pitch_role": "goalkeeper",
"checked_in": true
}
]
}
],
"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": [],
"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
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

Player-in-game object

Field Type Notes
id integer \ null
name string Display name
is_guest boolean true if not a registered user
is_captain boolean Team captain
is_substitute boolean On the bench
pitch_role string \ null
checked_in boolean Whether they checked in

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/games/thursday-kickabout/scores \
  -H "Authorization: Bearer <your_api_key>" \
  -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/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/games/thursday-kickabout/goals \
  -H "Authorization: Bearer <your_api_key>" \
  -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/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/games/thursday-kickabout/marker \
  -H "Authorization: Bearer <your_api_key>" \
  -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",
    "secondary_player_pc_id": "9021",
    "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 User ID of the primary player (scorer, shooter, outgoing sub…)
secondary_player_pc_id No User ID of the secondary player (assister, chance creator, incoming sub…)
label No Optional free-text note

Allowed type values

Category Values
Match lifecycle Start · Break · Resume · End · PenaltyShootout · ResetMatch
Match events Goal · Miss · Save · Shot · Foul · YellowCard · RedCard · Substitution · Penalty · PenaltyScored · PenaltyMissed · Event

Player field mapping by type

Type primary_player_pc_id secondary_player_pc_id
Goal Scorer Assister
Shot / Miss / Save Shooter Chance creator
Substitution Outgoing player Incoming player
Foul Foul committer Fouled player
Penalty Fouled player Foul committer
PenaltyScored Penalty taker
PenaltyMissed GK (if saved) / Taker (if missed) Taker (if saved) / —
YellowCard / RedCard Booked player
PenaltyShootout
ResetMatch
Start / Break / Resume / End / Event

Response
json
{ "status": "success", "marker_id": "e305e94b-4b10-4bc3-be9d-472d2fb2d2e1" }

Returns 201 Created for new markers, 200 OK for updates.


GET /api/v1/games/:slug/markers

Returns all markers for a game ordered by timestamp.

curl https://pitch-connect.space/api/v1/games/thursday-kickabout/markers \
  -H "Authorization: Bearer <your_api_key>"

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",
"secondary_player_pc_id": "9021",
"label": "Tipped over crossbar",
"primary_player": { "id": 8432, "name": "Alex Johnson" },
"secondary_player": { "id": 9021, "name": "Sam Smith" }
}
]
}

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.


Error Responses

HTTP Status Meaning
401 Unauthorized Missing or invalid token
404 Not Found Resource not found or doesn't belong to this club
422 Unprocessable Invalid request body

401 example
json
{
"error": "Invalid API token.",
"hint": "Get a key from your club's Integrations page or connect via OAuth."
}


Notes

  • All timestamps are UTC ISO 8601
  • GET /api/v1/club returns 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_position for lineup/formation — it returns club_position if set, otherwise falls back to preferred_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 client id to update without creating a duplicate
  • When game_mode is true, only simplified types are sent: Start, Goal, YellowCard, RedCard, Substitution, Break, Resume, PenaltyScored, PenaltyMissed, PenaltyShootout, ResetMatch — the structural Penalty marker is not transmitted in game mode
  • primary_player_pc_id / secondary_player_pc_id are the player's id from the /players endpoint
  • Write endpoints only accept games belonging to the club identified by the token
  • Per-app tokens can be revoked individually from /clubs/{your-club-slug}/integrations without affecting other connections