Skip to main content

Server API

Introduction

The tf2pickup.org public API provides read-only access to player profiles, game history, live queue state, and application metadata. It is designed for third-party dashboards, bots, and integrations.

Base URL: {WEBSITE_URL}/api/v1/

  • No authentication is required.
  • All endpoints are GET only.
  • All responses use the application/hal+json content type.
  • Dates are ISO 8601 UTC strings (e.g. "2024-01-01T00:00:00.000Z").
  • All /api/ responses include the Access-Control-Allow-Origin: * header, allowing cross-origin requests from any domain.

HAL Format

All responses follow the HAL (Hypertext Application Language) format.

  • _links — an object containing named hypermedia links. Each link has at minimum an href property. self always points to the current resource.
  • _embedded — an object containing related resources embedded inline in the response. Used in list endpoints to embed item arrays.

Example:

{
"_links": {
"self": { "href": "/api/v1/players" },
"next": { "href": "/api/v1/players?offset=20&limit=20" }
},
"_embedded": {
"players": []
}
}

Pagination

List endpoints support offset-based pagination via query parameters.

ParameterTypeDefaultMaxDescription
offsetinteger0Number of items to skip
limitinteger20100Maximum items to return

Paginated responses include:

  • total — total number of matching items
  • offset — the current offset
  • limit — the current limit
  • _links.next — link to the next page (omitted on the last page)
  • _links.prev — link to the previous page (omitted when offset === 0)

Endpoints

GET /api/v1/

Returns links to all top-level resources. Use this as the entry point to discover available endpoints.

Example request:

curl https://tf2pickup.example.com/api/v1/

Example response:

{
"_links": {
"self": { "href": "/api/v1" },
"players": { "href": "/api/v1/players" },
"games": { "href": "/api/v1/games" },
"queue": { "href": "/api/v1/queue" },
"onlinePlayers": { "href": "/api/v1/online-players" },
"version": { "href": "/api/v1/version" }
}
}

GET /api/v1/version

Returns the current application version string.

Example request:

curl https://tf2pickup.example.com/api/v1/version

Example response:

{
"version": "4.0.0",
"_links": {
"self": { "href": "/api/v1/version" }
}
}

GET /api/v1/players

Returns a paginated list of registered players, sorted by join date descending (newest first).

Query parameters:

ParameterTypeDefaultMaxDescription
offsetinteger0Number of items to skip
limitinteger20100Items per page

Example request:

curl "https://tf2pickup.example.com/api/v1/players?offset=0&limit=20"

Example response:

{
"total": 1234,
"offset": 0,
"limit": 20,
"_links": {
"self": { "href": "/api/v1/players?offset=0&limit=20" },
"next": { "href": "/api/v1/players?offset=20&limit=20" }
},
"_embedded": {
"players": [
{
"steamId": "76561198012345678",
"name": "PlayerName",
"joinedAt": "2024-01-01T00:00:00.000Z",
"avatar": {
"small": "https://avatars.steamstatic.com/abc_34.jpg",
"medium": "https://avatars.steamstatic.com/abc_64.jpg",
"large": "https://avatars.steamstatic.com/abc_full.jpg"
},
"roles": [],
"stats": {
"totalGames": 42,
"gamesByClass": { "soldier": 30, "scout": 12 }
},
"etf2lProfileId": 12345,
"twitchTvProfileUrl": "https://www.twitch.tv/somestreamer",
"activeGame": null,
"_links": {
"self": { "href": "/api/v1/players/76561198012345678" },
"games": { "href": "/api/v1/players/76561198012345678/games" }
}
}
]
}
}

GET /api/v1/players/:steamId

Returns a single player by their Steam ID 64.

Path parameters:

ParameterDescription
steamIdSteam ID 64 (17-digit number)

Example request:

curl https://tf2pickup.example.com/api/v1/players/76561198012345678

Example response:

{
"steamId": "76561198012345678",
"name": "PlayerName",
"joinedAt": "2024-01-01T00:00:00.000Z",
"avatar": {
"small": "https://avatars.steamstatic.com/abc_34.jpg",
"medium": "https://avatars.steamstatic.com/abc_64.jpg",
"large": "https://avatars.steamstatic.com/abc_full.jpg"
},
"roles": [],
"stats": {
"totalGames": 42,
"gamesByClass": { "soldier": 30, "scout": 12 }
},
"etf2lProfileId": 12345,
"twitchTvProfileUrl": "https://www.twitch.tv/somestreamer",
"activeGame": 1234,
"_links": {
"self": { "href": "/api/v1/players/76561198012345678" },
"games": { "href": "/api/v1/players/76561198012345678/games" }
}
}

Field notes:

  • roles[] for regular players; possible values are "admin" and "super user".
  • etf2lProfileId — ETF2L profile ID, or null if no ETF2L profile is linked.
  • twitchTvProfileUrl — Twitch channel URL, or null if no Twitch profile is linked.
  • activeGame — game number of the player's current active game, or null if not in a game.

GET /api/v1/players/:steamId/games

Returns a paginated list of games that the given player participated in, sorted by game number descending (most recent first).

Path parameters:

ParameterDescription
steamIdSteam ID 64 (17-digit number)

Query parameters:

ParameterTypeDefaultMaxDescription
offsetinteger0Number of items to skip
limitinteger10100Items per page

Example request:

curl "https://tf2pickup.example.com/api/v1/players/76561198012345678/games?offset=0&limit=10"

Example response:

{
"total": 42,
"offset": 0,
"limit": 10,
"_links": {
"self": { "href": "/api/v1/players/76561198012345678/games?offset=0&limit=10" },
"next": { "href": "/api/v1/players/76561198012345678/games?offset=10&limit=10" }
},
"_embedded": {
"games": [
{
"id": 1234,
"map": "cp_process_final",
"state": "ended",
"score": { "red": 3, "blu": 2 },
"logsUrl": "https://logs.tf/12345",
"demoUrl": "https://demos.tf/12345",
"createdAt": "2024-01-01T00:00:00.000Z",
"endedAt": "2024-01-01T01:00:00.000Z",
"gameServer": { "name": "EU #1", "provider": "static" },
"_links": {
"self": { "href": "/api/v1/games/1234" },
"slots": { "href": "/api/v1/games/1234/slots" },
"events": { "href": "/api/v1/games/1234/events" }
}
}
]
}
}

GET /api/v1/games

Returns a paginated list of games, sorted by game number descending (most recent first).

Query parameters:

ParameterTypeDefaultMaxDescription
offsetinteger0Number of items to skip
limitinteger20100Items per page
statestringFilter by game state. One of: created, configuring, launching, started, ended, interrupted

Example request:

curl "https://tf2pickup.example.com/api/v1/games?offset=0&limit=20&state=ended"

Example response:

{
"total": 5678,
"offset": 0,
"limit": 20,
"_links": {
"self": { "href": "/api/v1/games?offset=0&limit=20" },
"next": { "href": "/api/v1/games?offset=20&limit=20" }
},
"_embedded": {
"games": [
{
"id": 1234,
"map": "cp_process_final",
"state": "ended",
"score": { "red": 3, "blu": 2 },
"logsUrl": "https://logs.tf/12345",
"demoUrl": "https://demos.tf/12345",
"createdAt": "2024-01-01T00:00:00.000Z",
"endedAt": "2024-01-01T01:00:00.000Z",
"gameServer": { "name": "EU #1", "provider": "static" },
"_links": {
"self": { "href": "/api/v1/games/1234" },
"slots": { "href": "/api/v1/games/1234/slots" },
"events": { "href": "/api/v1/games/1234/events" }
}
}
]
}
}

GET /api/v1/games/:id

Returns a single game by its game number.

Path parameters:

ParameterDescription
idGame number (integer)

Example request:

curl https://tf2pickup.example.com/api/v1/games/1234

Example response:

{
"id": 1234,
"map": "cp_process_final",
"state": "ended",
"score": { "red": 3, "blu": 2 },
"logsUrl": "https://logs.tf/12345",
"demoUrl": "https://demos.tf/12345",
"createdAt": "2024-01-01T00:00:00.000Z",
"endedAt": "2024-01-01T01:00:00.000Z",
"gameServer": { "name": "EU #1", "provider": "static" },
"_links": {
"self": { "href": "/api/v1/games/1234" },
"slots": { "href": "/api/v1/games/1234/slots" },
"events": { "href": "/api/v1/games/1234/events" }
}
}

Field notes:

  • scorenull for games that have not ended.
  • logsUrl / demoUrlnull if not yet uploaded.
  • endedAtnull for active games.
  • gameServernull if no server has been assigned yet.
  • state — one of created, configuring, launching, started, ended, interrupted.

GET /api/v1/games/:id/slots

Returns all player slot assignments for a game.

Path parameters:

ParameterDescription
idGame number (integer)

Example request:

curl https://tf2pickup.example.com/api/v1/games/1234/slots

Example response:

{
"_links": {
"self": { "href": "/api/v1/games/1234/slots" },
"game": { "href": "/api/v1/games/1234" }
},
"_embedded": {
"slots": [
{
"id": "slot-0",
"player": {
"steamId": "76561198012345678",
"name": "PlayerName",
"_links": { "self": { "href": "/api/v1/players/76561198012345678" } }
},
"team": "red",
"gameClass": "soldier",
"status": "active",
"connectionStatus": "connected"
}
]
}
}

Field notes:

  • team"red" or "blu".
  • status"active" or "waiting for substitute".
  • connectionStatus"offline", "joining", or "connected".

GET /api/v1/games/:id/events

Returns the ordered event log for a game in chronological order. Internal and administrative events are excluded.

Path parameters:

ParameterDescription
idGame number (integer)

Example request:

curl https://tf2pickup.example.com/api/v1/games/1234/events

Example response:

{
"_links": {
"self": { "href": "/api/v1/games/1234/events" },
"game": { "href": "/api/v1/games/1234" }
},
"_embedded": {
"events": [
{ "type": "gameCreated", "at": "2024-01-01T00:00:00.000Z" },
{ "type": "gameServerAssigned", "at": "2024-01-01T00:01:00.000Z", "gameServerName": "EU #1" },
{ "type": "gameStarted", "at": "2024-01-01T00:05:00.000Z" },
{
"type": "roundEnded",
"at": "2024-01-01T00:10:00.000Z",
"winner": "red",
"lengthMs": 300000,
"score": { "red": 1, "blu": 0 }
},
{
"type": "substituteRequested",
"at": "2024-01-01T00:12:00.000Z",
"gameClass": "medic"
},
{ "type": "playerReplaced", "at": "2024-01-01T00:14:00.000Z", "gameClass": "medic" },
{ "type": "gameEnded", "at": "2024-01-01T01:00:00.000Z", "reason": "match ended" }
]
}
}

Included event types:

Event typeAdditional fieldsDescription
gameCreatedGame was created
gameServerAssignedgameServerNameA game server was assigned
gameServerInitializedGame server finished configuration
gameServerConfigureFailedGame server configuration failed
gameStartedGame became live
gameRestartedGame server was restarted
roundEndedwinner, lengthMs, scoreA round finished
substituteRequestedgameClassA substitute was requested
playerReplacedgameClassA player was substituted
playerJoinedGameServerplayer (steamId)A player connected to the game server
playerJoinedGameServerTeamplayer (steamId), teamA player joined their assigned team
playerLeftGameServerplayer (steamId)A player disconnected from game server
gameEndedreasonGame ended

gameEnded.reason is one of "match ended", "interrupted", or "too many substitute requests".


GET /api/v1/queue

Returns the current live queue state, including slot occupancy and map vote results.

Example request:

curl https://tf2pickup.example.com/api/v1/queue

Example response:

{
"state": "waiting",
"config": {
"teamCount": 2,
"classes": [
{ "name": "scout", "count": 2 },
{ "name": "soldier", "count": 2 },
{ "name": "demoman", "count": 1 },
{ "name": "medic", "count": 1, "canMakeFriendsWith": ["scout", "soldier", "demoman"] }
]
},
"slots": [
{ "id": "0", "gameClass": "scout", "player": null, "ready": false },
{
"id": "1",
"gameClass": "scout",
"player": {
"steamId": "76561198012345678",
"name": "PlayerName",
"avatarUrl": "https://avatars.steamstatic.com/abc_64.jpg"
},
"ready": true
}
],
"mapVoteResults": {
"cp_process_final": 3,
"cp_badlands": 1,
"cp_metalworks": 0
},
"_links": {
"self": { "href": "/api/v1/queue" }
}
}

Field notes:

  • state"waiting", "ready", or "launching".
  • slots[].playernull if the slot is empty.
  • mapVoteResults — map names to vote counts. Empty object {} if no map vote is in progress.
  • config.classes[].canMakeFriendsWith — optional; present only for classes that support the friend system.

GET /api/v1/online-players

Returns all players currently connected to the site.

Example request:

curl https://tf2pickup.example.com/api/v1/online-players

Example response:

{
"count": 42,
"_links": {
"self": { "href": "/api/v1/online-players" }
},
"_embedded": {
"players": [
{
"steamId": "76561198012345678",
"name": "PlayerName",
"avatar": "https://avatars.steamstatic.com/abc_64.jpg",
"_links": {
"self": { "href": "/api/v1/players/76561198012345678" }
}
}
]
}
}

Error Responses

All errors return a standard JSON body with an HTTP status code.

{
"statusCode": 404,
"error": "Not Found",
"message": "Player not found"
}
StatusErrorScenario
400Bad RequestInvalid query parameters (e.g. non-numeric offset)
404Not FoundThe requested player or game does not exist
500Internal Server ErrorUnexpected server error