CS2 Positional Data API
Where things happened on the map, from the parsed demo of each pro map. Every grenade (smoke, flash, HE, molotov, decoy) with the spot it went off, who threw it, the round, seconds into the round and how long smokes and molotovs lasted; every bomb plant, defuse and explosion with its site; and every kill with the attacker's and victim's position and callout (BombsiteA, Banana). Each point comes in game units (x, y, z) and already projected onto the map's 1024x1024 radar as radar.x / radar.y from 0 to 1, so you can draw it on any radar image by multiplying by its width and height; /cs2/map-radars has the calibration per map. Heatmaps bin any of these over a grid laid on the radar, per map or per player. Grenade positions cover every demo-parsed map; kill and death positions cover demos parsed from 31 October 2026 on. Not included: where a grenade was thrown from, and player movement paths. Positional data is on paid plans.
5 endpoints in this section. Authenticate with the x-api-key header; every response is { success, data, meta }. Other sections: Live, Player props, Odds, Matches, Maps & rounds, Rankings, Teams, Players, Events, Stats & leaderboards, Skins, Transfers, Search & reference.
Map grenade landing spots
Landing spots only, not lineups: where each grenade went off, never where the player stood to throw it. Every smoke, flash, HE grenade, molotov/incendiary and decoy thrown on one map: where it went off, who threw it (player, team, side), round, `roundSeconds` since the round went live, and how long smokes and molotovs lasted (`durationSeconds`). `summary` counts them by type, side and player. Where a grenade was thrown from is not recorded (`thrownFrom` is null). Coordinates are game units from the demo (`position.x/y/z`); `radar.x/y` is the same point on the map's 1024x1024 radar overview, 0..1 from the top-left (multiply by your image size; `radar.level` is upper/lower on Nuke and Vertigo). Calibration per map: `/cs2/map-radars`. Paid plans; Free accounts created from 2026-10-28 get 403 PLAN_REQUIRED (the dashboard sandbox shows it). These routes are equivalent; use whichever reads best.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| gameId (path)* | string | Game (map) id from /cs2/matches/{matchId}/games. |
| player | string | Thrower: player id, nickname or steamid. |
| round | string | Only this round number. |
| side | string | T or CT: the thrower's side. |
| team | string | Thrower's team: id or name. |
| type | string | Grenade type: smoke, flash, he, molotov (incendiaries included) or decoy. |
Example
curl "https://api.citoapi.com/api/v1/cs2/games/GAME_ID/utility" \
-H "x-api-key: YOUR_API_KEY"Response
{
"success": true,
"data": {
"entityType": "cs2_map_utility",
"mapId": "cs2-match-2399437-map-1",
"matchId": "cs2-match-2399437",
"mapNumber": 1,
"mapName": "Ancient",
"radarCalibration": {
"mapName": "Ancient",
"radarSize": 1024,
"posX": -2953,
"posY": 2164,
"scale": 5,
"lowerLevelMaxZ": null
},
"coordinates": {
"units": "Game units from the demo (x, y, z). z is height.",
"radar": "radar.x / radar.y place the point on the map's radar overview, 0..1 from the top-left corner. Multiply by your radar …",
"formula": "radar.x = (x - posX) / scale / radarSize; radar.y = (posY - y) / scale / radarSize (radarSize = 1024)",
"calibrationEndpoint": "/api/v1/cs2/map-radars"
},
"note": "Grenade positions are where each grenade went off (smoke bloom, flash pop, HE blast, molotov fire). Where it was thro…",
"summary": {
"total": 302,
"byType": {
"flash": 82,
"smoke": 87,
"he": 58,
"molotov": 75
},
"bySide": {
"T": 127,
"CT": 175
},
"byPlayer": [
{
"playerId": "cs2-player-24806",
"playerName": "frontales",
"teamName": "HOTU",
"total": 44,
"byType": {
"smoke": 13,
"he": 5,
"flash": 16,
"molotov": 10
}
},
{
"playerId": "cs2-player-18752",
"playerName": "kade0",
"teamName": "HOTU",
"total": 38,
"byType": {
"smoke": 13,
"molotov": 11,
"he": 9,
"flash": 5
}
}
],
"avgSmokeSeconds": 22.1,
"avgMolotovSeconds": 5.9
},
"grenades": [
{
"round": 1,
"type": "flash",
"weapon": "Flashbang",
"tick": 824,
"matchSeconds": 12.875,
"roundSeconds": 12.9,
"endedTick": null,
"durationSeconds": null,
"position": {
"x": 773.5,
"y": 88.1,
"z": 393.9
},
"radar": {
"x": 0.7278,
"y": 0.4054,
"level": null
},
"thrower": {
"playerId": "cs2-player-19957",
"playerName": "Bart4k",
"teamId": "cs2-team-12510",
"teamName": "The Huns",
"side": "T"
},
"thrownFrom": null
},
{
"round": 1,
"type": "smoke",
"weapon": "Smoke Grenade",
"tick": 849,
"matchSeconds": 13.266,
"roundSeconds": 13.3,
"endedTick": 2261,
"durationSeconds": 22.1,
"position": {
"x": 485.9,
"y": 251.6,
"z": 154.9
},
"radar": {
"x": 0.6717,
"y": 0.3735,
"level": null
},
"thrower": {
"playerId": "cs2-player-26013",
"playerName": "Darkmeister",
"teamId": "cs2-team-12510",
"teamName": "The Huns",
"side": "T"
},
"thrownFrom": null
}
]
},
"meta": {
"count": 302,
"totalUnfiltered": 302,
"dataAvailable": true,
"filters": {},
"grenadeTypes": [
"smoke",
"flash"
],
"thrownFromAvailable": false,
"limit": 302,
"total": 302,
"page": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Map positions
Every positional event on one map in one time-ordered list: grenades, bomb plants / defuses / explosions (with site), and kills with the attacker and victim position and callout (`attackerPlace`, `victimPlace`, e.g. BombsiteA, Banana). Kill positions exist for demos parsed from 2026-10-31 on; earlier kills return null positions and `meta.counts.killsWithPositions` says how many have them. Coordinates are game units from the demo (`position.x/y/z`); `radar.x/y` is the same point on the map's 1024x1024 radar overview, 0..1 from the top-left (multiply by your image size; `radar.level` is upper/lower on Nuke and Vertigo). Calibration per map: `/cs2/map-radars`. Paid plans; Free accounts created from 2026-10-28 get 403 PLAN_REQUIRED (the dashboard sandbox shows it). These routes are equivalent; use whichever reads best.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| gameId (path)* | string | Game (map) id from /cs2/matches/{matchId}/games. |
| kind | string | Comma-separated: grenade, bomb, kill (default all three). |
| kinds | string | Filter by kinds. |
| round | string | Only this round number. |
| side | string | T or CT. |
Example
curl "https://api.citoapi.com/api/v1/cs2/games/GAME_ID/positions" \
-H "x-api-key: YOUR_API_KEY"Response
{
"success": true,
"data": {
"entityType": "cs2_map_positions",
"mapId": "cs2-match-2399437-map-1",
"matchId": "cs2-match-2399437",
"mapNumber": 1,
"mapName": "Ancient",
"radarCalibration": {
"mapName": "Ancient",
"radarSize": 1024,
"posX": -2953,
"posY": 2164,
"scale": 5,
"lowerLevelMaxZ": null
},
"coordinates": {
"units": "Game units from the demo (x, y, z). z is height.",
"radar": "radar.x / radar.y place the point on the map's radar overview, 0..1 from the top-left corner. Multiply by your radar …",
"formula": "radar.x = (x - posX) / scale / radarSize; radar.y = (posY - y) / scale / radarSize (radarSize = 1024)",
"calibrationEndpoint": "/api/v1/cs2/map-radars"
},
"notes": [
"Grenade positions are where each grenade went off (smoke bloom, flash pop, HE blast, molotov fire). Where it was thro…",
"Kill and death positions are recorded for demos parsed from 2026-10-31 on. This map was parsed earlier, so its kills …"
],
"events": [
{
"kind": "grenade",
"round": 1,
"type": "flash",
"weapon": "Flashbang",
"tick": 824,
"matchSeconds": 12.875,
"roundSeconds": 12.9,
"endedTick": null,
"durationSeconds": null,
"position": {
"x": 773.5,
"y": 88.1,
"z": 393.9
},
"radar": {
"x": 0.7278,
"y": 0.4054,
"level": null
},
"thrownFrom": null,
"player": {
"playerId": "cs2-player-19957",
"playerName": "Bart4k",
"teamId": "cs2-team-12510",
"teamName": "The Huns",
"side": "T"
}
},
{
"kind": "kill",
"type": "kill",
"round": 1,
"tick": 836,
"matchSeconds": 13.063,
"weapon": "USP-S",
"headshot": true,
"attacker": {
"playerId": "cs2-player-21385",
"playerName": "mizu",
"teamId": "cs2-team-11581",
"teamName": "HOTU",
"side": "CT"
},
"victim": {
"playerId": "cs2-player-26124",
"playerName": "bexyz",
"teamId": "cs2-team-12510",
"teamName": "The Huns",
"side": "T"
},
"attackerPosition": null,
"attackerRadar": null,
"attackerPlace": null,
"victimPosition": null,
"victimRadar": null,
"victimPlace": null
}
]
},
"meta": {
"count": 393,
"dataAvailable": true,
"kinds": [
"grenade",
"bomb"
],
"filters": {},
"counts": {
"grenades": 302,
"bombEvents": 0,
"kills": 91,
"killsWithPositions": 0
},
"limit": 393,
"total": 393,
"page": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Map heatmap
Counts per cell of a grid laid over the radar for one kind of event: `kind=utility` (all grenades, the default), `smoke`, `flash`, `he`, `molotov`, `decoy`, `bomb_plant`, `bomb_defuse`, `kill` (where the killer stood) or `death` (where the victim died). `cells[]` = {gx, gy, count, share}, gx/gy from the top-left of a `grid` x `grid` overlay. Coordinates are game units from the demo (`position.x/y/z`); `radar.x/y` is the same point on the map's 1024x1024 radar overview, 0..1 from the top-left (multiply by your image size; `radar.level` is upper/lower on Nuke and Vertigo). Calibration per map: `/cs2/map-radars`. Paid plans; Free accounts created from 2026-10-28 get 403 PLAN_REQUIRED (the dashboard sandbox shows it). These routes are equivalent; use whichever reads best.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| gameId (path)* | string | Game (map) id from /cs2/matches/{matchId}/games. |
| grid | string | Cells per side, 4-128 (default 32). |
| kind | string | utility (default), smoke, flash, he, molotov, decoy, bomb_plant, bomb_defuse, kill or death. |
| player | string | Only this player (id or nickname). |
| side | string | T or CT. |
| type | string | Filter by type. |
Example
curl "https://api.citoapi.com/api/v1/cs2/games/GAME_ID/heatmap" \
-H "x-api-key: YOUR_API_KEY"Response
{
"success": true,
"data": {
"entityType": "cs2_map_heatmap",
"mapId": "cs2-match-2399437-map-1",
"matchId": "cs2-match-2399437",
"mapName": "Ancient",
"kind": "utility",
"radarCalibration": {
"mapName": "Ancient",
"radarSize": 1024,
"posX": -2953,
"posY": 2164,
"scale": 5,
"lowerLevelMaxZ": null
},
"cellMeaning": "cells[].gx / gy index a 32x32 grid over the radar from the top-left; one cell is 32 radar pixels square. share = coun…",
"grid": 32,
"points": 302,
"offRadar": 0,
"maxCount": 27,
"cells": [
{
"gx": 13,
"gy": 18,
"count": 27,
"share": 0.0894
},
{
"gx": 15,
"gy": 12,
"count": 15,
"share": 0.0497
}
]
},
"meta": {
"count": 96,
"dataAvailable": true,
"filters": {},
"kinds": [
"utility",
"smoke"
],
"limit": 96,
"total": 96,
"page": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Map radar calibration
For each map, the origin (`posX`, `posY`) and `scale` that turn game coordinates into a point on its 1024x1024 radar overview: `radar.x = (x - posX) / scale / 1024`, `radar.y = (posY - y) / scale / 1024`. Two-level maps carry `lowerLevelMaxZ` (z at or below it is the lower level). Free on every plan.
Example
curl "https://api.citoapi.com/api/v1/cs2/map-radars" \
-H "x-api-key: YOUR_API_KEY"Response
{
"success": true,
"data": {
"entityType": "cs2_map_radars",
"coordinates": {
"units": "Game units from the demo (x, y, z). z is height.",
"radar": "radar.x / radar.y place the point on the map's radar overview, 0..1 from the top-left corner. Multiply by your radar …",
"formula": "radar.x = (x - posX) / scale / radarSize; radar.y = (posY - y) / scale / radarSize (radarSize = 1024)",
"calibrationEndpoint": "/api/v1/cs2/map-radars"
},
"radars": [
{
"mapName": "Ancient",
"radarSize": 1024,
"posX": -2953,
"posY": 2164,
"scale": 5,
"lowerLevelMaxZ": null
},
{
"mapName": "Anubis",
"radarSize": 1024,
"posX": -2796,
"posY": 3328,
"scale": 5.22,
"lowerLevelMaxZ": null
}
]
},
"meta": {
"count": 10,
"dataAvailable": true,
"limit": 10,
"total": 10,
"page": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Player grenade landing spots
Landing spots only, not lineups (where the player stood to throw is not recorded). Where a player's grenades land across their recent maps: every grenade with position, radar point, round and duration, plus counts by type, side and map. Pass `map` (e.g. `Mirage`) to get a `heatmap` for that map; coordinates differ per map. Coordinates are game units from the demo (`position.x/y/z`); `radar.x/y` is the same point on the map's 1024x1024 radar overview, 0..1 from the top-left (multiply by your image size; `radar.level` is upper/lower on Nuke and Vertigo). Calibration per map: `/cs2/map-radars`. Paid plans; Free accounts created from 2026-10-28 get 403 PLAN_REQUIRED (the dashboard sandbox shows it).
Query parameters
| Parameter | Type | Description |
|---|---|---|
| playerIdOrSlug (path)* | string | Player id (cs2-player-11893) or slug (zywoo). |
| days | integer | Look-back window in days (default 180). |
| grid | string | Heatmap cells per side, 4-128 (default 32). |
| limit | integer | Rows to return. Maximum 5000; larger values are reduced to 5000. |
| map | string | Map name (Mirage, Inferno...). Adds a heatmap. |
| maps | string | Most recent maps to include (default 60, max 200). |
| round | string | Only this round number. |
| side | string | T or CT: the thrower's side. |
| type | string | Grenade type: smoke, flash, he, molotov (incendiaries included) or decoy. |
Example
curl "https://api.citoapi.com/api/v1/cs2/players/donk/utility" \
-H "x-api-key: YOUR_API_KEY"Response
{
"success": true,
"data": {
"entityType": "cs2_player_utility",
"playerId": "cs2-player-11893",
"playerNickname": "ZywOo",
"mapName": null,
"days": 180,
"mapsPlayed": 60,
"radarCalibration": null,
"coordinates": {
"units": "Game units from the demo (x, y, z). z is height.",
"radar": "radar.x / radar.y place the point on the map's radar overview, 0..1 from the top-left corner. Multiply by your radar …",
"formula": "radar.x = (x - posX) / scale / radarSize; radar.y = (posY - y) / scale / radarSize (radarSize = 1024)",
"calibrationEndpoint": "/api/v1/cs2/map-radars"
},
"note": "Grenade positions are where each grenade went off (smoke bloom, flash pop, HE blast, molotov fire). Where it was thro…",
"summary": {
"total": 2260,
"byType": {
"he": 333,
"molotov": 481,
"flash": 805,
"smoke": 641
},
"bySide": {
"CT": 1127,
"T": 1133
},
"avgSmokeSeconds": 22.1,
"avgMolotovSeconds": 6,
"byMap": {
"Nuke": 219,
"Dust2": 634,
"Inferno": 317,
"Anubis": 295,
"Cache": 408,
"Mirage": 387
}
},
"heatmap": null,
"grenades": [
{
"round": 2,
"type": "he",
"weapon": "HE Grenade",
"tick": 8901,
"matchSeconds": 139.078,
"roundSeconds": 26.8,
"endedTick": null,
"durationSeconds": null,
"position": {
"x": 1019,
"y": -1778.6,
"z": -388.5
},
"radar": {
"x": 0.6239,
"y": 0.6509,
"level": "upper"
},
"thrower": {
"playerId": "cs2-player-11893",
"playerName": "ZywOo",
"teamId": "cs2-team-9565",
"teamName": "Vitality",
"side": "CT"
},
"thrownFrom": null,
"mapId": "cs2-match-2396567-map-1",
"mapName": "Nuke"
},
{
"round": 2,
"type": "he",
"weapon": "HE Grenade",
"tick": 9275,
"matchSeconds": 144.922,
"roundSeconds": 32.7,
"endedTick": null,
"durationSeconds": null,
"position": {
"x": 1009.6,
"y": -1826.7,
"z": -394.7
},
"radar": {
"x": 0.6226,
"y": 0.6576,
"level": "upper"
},
"thrower": {
"playerId": "cs2-player-11893",
"playerName": "ZywOo",
"teamId": "cs2-team-9565",
"teamName": "Vitality",
"side": "CT"
},
"thrownFrom": null,
"mapId": "cs2-match-2396567-map-1",
"mapName": "Nuke"
}
]
},
"meta": {
"count": 2,
"total": 2260,
"dataAvailable": true,
"filters": {
"days": 180
},
"heatmapNote": "Pass ?map= (for example map=Mirage) to get a heatmap; coordinates differ per map.",
"limit": 2,
"page": 1,
"totalPages": 1130,
"hasNext": true,
"hasPrev": false
}
}Try the CS2 Positional Data API
Run a real request with your key, then wire the same call into your app. The free plan includes 500 requests a month.
curl "https://api.citoapi.com/api/v1/cs2/maps/cs2-match-2399437-map-1/utility?type=smoke&side=T" \
-H "x-api-key: YOUR_API_KEY"