The local HUD API provides your HUD with the conditioned state of the current CS2 cast. The connection is made directly to the running sOU app on the same computer. The SDK client receives complete game states via SignalR and uses the data contracts from SOU.Live.Contracts. Start with HUD Development Guide: recommended is Blazor WebAssembly; web projects are also fully and permanently supported. The tools and clients are available via NuGet. In addition, Full searchable type reference and SDK contract file as a download are available.
What data do I need for my display?
For a new HUD start with the Installable SDK template. Your client establishes the connection; you supplement the desired display in the reception code. You need the endpoint description in this chapter especially if you want to implement the transport yourself or investigate a connection error.
| Your display | Starting point in the snapshot | Pay attention to: |
|---|---|---|
| Map, score and round | game | Before the first game data reception, game is empty. Show a waiting state. |
| Prepared opponents, series, veto and start time | managedMatch | It may exist before the game. CT/T assignment is not a permanent team identity. |
| Player card, life points and weapons | game.observedPlayer or game.players | Missing values do not interpret as 0 or death. Profiles and live player data differ. |
| Recognizing obsolete game displays | freshness | An active HUD connection does not guarantee new CS2 data. |
| Operational headline, sponsor image or animation | Panelstream | Panel messages are received separately from the game snapshot. Settings are state; buttons send actions. |
The code examples use JavaScript properties in camelCase. In C#, for example, the same properties are called Game, ObservedPlayer and ManagedMatch. The Type reference lists each field; this chapter explains how to use the delivered values technically.
Version and General Conventions
- The current main schema version is 2.
schemaVersionin the snapshot andapiVersionin the package manifest must fit this contract. - JSON properties and enum values are camelCase:
roundNumber,counterTerrorists,freezeTime. Numerical enum order values are not part of the protocol. - A missing optional value is
null. An existing unknown enumname becomesunknown. An incorrect data type is not a valid substitute for a missing value. - Steam IDs and technical identifiers remain strings. Do not convert Steam IDs into JavaScript numbers; accuracy can be lost.
- UUIDs are transmitted as strings,
DateTimeOffsetvalues as ISO 8601 time points with offset.receivedAtis reception time, not play time. - Round numbers, map numbers, and veto step numbers begin at 1, while observer digits follow
1…9,0. - Time durations are numerical seconds, recognizable by names like
secondsRemaining. Vectors use unchanged CS2 world units. nullfor an optional list means not available;[]means explicitly empty. With the always existing player list,availableSectionsalso helps.
Additive fields can be added within v2. A consumer must tolerate unknown JSON properties and represent a suitable unknown state for new enum values. The SDK version and your package version will be versioned independently. Old v1 addresses are not redirected to v2 as aliases.
Endpoints and access
The local host listens to http://127.0.0.1:5085 by default. The port is configurable; use the origin of the loaded packet address in the HUD. Fixed cloud or standard port addresses prevent the correct operation in a different local configuration.
| Endpoint | Method/transport | Purpose and access |
|---|---|---|
/hud-browser/session | POST, header X-SOU-HUD: 1 | Creates/renews the local browser session; reply 204 and HttpOnly cookie. |
/hud-live/v2 | SignalR, Stream Watch() | Recent snapshot and full updates for browser HUDs; browser session required. |
/hud-live/panels | SignalR, Stream Watch(hudId, version) | Panel state and new actions for this version; browser session required. |
/cast-media/{name} | GET | Managed local images; browser session required. transfer file names from supplied media paths. |
/hud-browser/options | GET | Existing common display options with management metadata for older consumers; browser session required. Receive new package-related settings via panels. |
The host only accepts loopback connections and the hostnames 127.0.0.1 or localhost. If an origin is sent along, it must fit exactly with the local HTTP origin including host and port. A missing or incorrect browser cookie results in HTTP 401 for the HUD streams; an unauthorized origin or a missing session header results in HTTP 403. This is not a LAN API.
The browser cookie is HttpOnly and SameSite=Strict; its name contains the local port. It is generated by the host and renewed after the host restart. The SDK performs session creation before each connection attempt. No access data in the packet is required for this connection. Account passwords or access tokens do not belong in package files, browser addresses or JavaScript.
The HUD connection is used to read game states, media and panel messages. It offers neither write access to live data nor camera control or account management. Complete GSI raw packages and authentication data contained therein are not passed on to the HUD. More about setting up the data sources under CS2 and HLAE.
Add local rosters and profile images
The existing GET endpoint /hud-browser/options complements the live snapshot with stored metadata. It uses the same local browsing session. Only call it after successful SDK connection against the origin of the loaded packet address. A failed retrieval must not block the existing live display. Own HUD settings still belong in the package-related panel.
For rosters and portraits, this section of the JSON answer is sufficient. The names and identifiers are examples; the answer may include other older display options:
{
"players": {
"76561198000000001": {
"name": "Beispielspieler",
"avatar": "/cast-media/beispiel.png",
"camera": null
}
},
"match": {
"left": { "id": "team-a", "name": "Beispiel A", "shortName": "A", "logo": null, "steamIds": ["76561198000000001"] },
"right": { "id": "team-b", "name": "Beispiel B", "shortName": "B", "logo": null, "steamIds": [] }
}
}
players is a dictionary indexed by Steam ID. Name and avatar are optional; camera is not a guaranteed available or freely embeddable video stream. match.left and match.right describe the managed teams A and B, not a CT/T page. The answer does not contain a reliable match ID of its own. Check the team IDs against the current managedMatch, use only its actual squad and discard a delayed response after a match/session change.
Use your own JSON adapter if needed; this older metadata form is not an additional DTO in the live snapshot. Limit repeated retrievals, set time/size limits and break them when closing. For media continue to use hud.media(path) or HudClient.Media(path). Do not uncheck foreign image/camera addresses. If the metadata is missing, the actual delivered live players and public panel references remain usable.
Stream, reconnection and data freshness
Watch immediately delivers the current full snapshot and then complete new states. These are not JSON patches. With slow reading, intermediate revisions can be omitted; therefore, process each received snapshot as a complete state and do not rely on a seamless sequence of individual changes.
The SDK clients report connecting, connected, reconnecting and stopped separately per channel. When transport is terminated, they re-create the browser session and subscribe again; between attempts is about one second. This does not require a new page structure. The treatment of an interrupted or outdated presentation remains the task of your HUD.
freshness refers to the reception of GSI. By default, the condition becomes 15 seconds without new GSI stale and 45 seconds disconnected. The thresholds are local host configuration. A working SignalR stream can simultaneously deliver an obsolete GSI state. Library and HLAE changes do not make GSI artificially fresh.
LiveSnapshot
| Field | Type | Meaning |
|---|---|---|
schemaVersion | Integers | Currently 2. |
sessionId | UUID | Identity of the game session. When changing, reset your own session-bound buffers. |
revision | 64-bit integer | Increases within the session in case of state changes, including freshness or administration changes. |
receivedAt | Date or null | UTC reception time of the last accepted GSI packet; empty before the first reception. |
freshness | DataFreshness | unknown, waiting, live, stale or disconnected. |
game | GameState or null | Prepared state of play. Before the first GSI no invented game object. |
managedMatch | ManagedMatch or null | Active managed match, also available before GSI. |
{
"schemaVersion": 2,
"sessionId": "00000000-0000-4000-8000-000000000001",
"revision": 0,
"receivedAt": null,
"freshness": "waiting",
"game": null,
"managedMatch": null
}
The example describes an empty initial state. The actual sessionId is awarded at term. It can change, among other things, at the first GSI message, a map change, a decreasing round number or at the transition from gameOver. Then, reset session-bound animations and cache storage. The ID does not describe a complete demo timeline.
GameState: match and game sections
| Field | Type | Meaning |
|---|---|---|
map, mapKind | String? ; MapKind? | Original map identifier and typed known map. Open identifier is retained for unknown type. |
phase, roundPhase | MatchPhase?; RoundPhase? | Match or round condition. |
roundNumber | Integers? | Round number from 1, not a zero-based source number. |
ct, t | SideState? | Current CS2 sides including score; no permanent team identity. |
players | Array from PlayerState | Always present. Empty array with missing player area or no visible player. |
observedPlayer | PlayerState? | Currently observed player, if determinable. |
currentPlayer | PlayerState? | GSI-player block; can describe local menu players and is not automatically the observed Pawn. |
mode | GameMode? | Play mode. |
roundWinner, roundWinReason, roundBomb | TeamSide?; RoundWinReason?; BombStatus? | Round exit supplied by the game and bomb status of the round. |
roundWins | Array of RoundResult or null | Round history sorted by number with roundNumber, winner and reason. |
countdown | CountdownState? | phase and last reported remaining seconds. |
bomb | BombState? | Bomb status, player reference, position and remaining seconds. |
grenades | Array of GrenadeState or null | Current grenades or inferno areas. |
provider | ProviderState? | Name, App ID, version, Steam ID and source time of the GSI provider. |
tournamentDraft | TournamentDraftState? | Game-reported tournament/draft metadata, regardless of managed veto. |
matchesToWinSeries, currentSpectators, souvenirsTotal | You integers? | Optional GSI series and viewer information. |
availableSections | Array from GsiSection | Which known GSI ranges actually existed. |
managedMatch | ManagedMatch? | Compatible access to the same active match; new code can use the field on the snapshot. |
radar | RadarState? | Optional current HLAE geometry, separated from the GSI player fields. |
killFeed | Array from KillFeedEntry | Limited stock of real HLAE death events; standard is an empty list. |
SideState contains name: string?, score: int?, flag: string?, consecutiveRoundLosses: int?, timeoutsRemaining: int? and matchesWon: int?. The names reported to CT/T are not a substitute for the central resolution in managedMatch.
Players, equipment and statistics
Player names in snapshots, killfeed, profiles and panel references are text, not HTML. Use normal text binding in Blazor or textContent in JavaScript. Do not insert unchecked names with innerHTML or MarkupString, and do not decode them again as HTML in the HUD. The current desktop output resolves one existing HTML entity layer in older source or profile names at the output boundary, so an apostrophe, for example, arrives as a character. JSON escapes such as \u0027 are already converted into the corresponding character by the JSON parser and are not HTML entities.
| PlayerState field | Type | Meaning |
|---|---|---|
steamId, name, clan | Any string? | Source identity and name. |
side, activity | TeamSide?; PlayerActivity? | Current game page and activity. |
health, armor | You integers? | health and armaments. Missing HP is not a death. |
isAlive | Boolean? | Derived from existing HP: greater than zero. Without HP null. |
helmet, defuseKit | You Boolean? | Equipment present or unknown. |
money, equipmentValue | You integers? | Money and equipment value. |
flashed, smoked, burning | You integers? | GSI effect values; do not interpret as seconds without its own scale. |
roundKills, roundHeadshotKills, roundDamage | You integers? | Reported values of the current round. |
observerSlot | Integers? | HUD code 1…9,0. The manager normalizes and condenses after coach masking; then not necessarily the unchanged native CS2 key assignment. |
spectationTargetSteamId | String? | Explicit reference to the observation target, if supplied. |
xpOverloadLevel | Integers? | Optional GSI. |
position, forward | Vector3State? | GSI position or direction vector. |
stats | MatchStats? | kills, assists, deaths, mvps, score, each integer? . |
recordedStats | RecordedPlayerStats? | Centrally co-written statistics of fully observed completed rounds. |
weapons | Array of WeaponState or null | Reported weapons/equipment. |
activeWeapon | WeaponState? | First weapon with state: "active", otherwise null. |
Profiles marked as a coach do not appear in the player, radar and killfeed data of the HUD. Use the delivered Observer digits unchanged. Derive team identities from the assigned teams, not from the position of a player in the list.
WeaponState
One weapon contains slot: string, name: string?, kind: WeaponKind?, type: WeaponCategory?, state: WeaponStatus?, paintKit: string?, ammoClip: int?, ammoClipMax: int? and ammoReserve: int?. name receives the open source identifier, kind denotes a known concrete weapon, type its category. Do not equate an unknown weapon identifier with a name prefix of a known weapon.
Distinguish a missing weapon from an existing weapon with an unknown identifier: weapons: [] is an explicitly empty inventory, weapons: null is an undelivered area. The active weapon display requires an actual entry with state: "active" or "reloading"; a holstered weapon is not a replacement. Without this entry, Waffenicon, weapon name and ammunition remain empty. In a safely dead player, display no old inventory entries as current equipment; lack of health alone means no death. Only an actually existing, unknown weapon identifier requires a corresponding icon reversion. Historical killfeed weapons are preserved regardless.
RecordedPlayerStats
The fields are rounds: int, damage: long, kills: long, headshotKills: long and coversWholeMatch: bool. averageDamagePerRound: double? is damage divided by recorded completed rounds; without such rounds null. headshotPercentage: double? is the headshot share of the recorded kills; without rounds or kills null.
coversWholeMatch decides whether you can call the value a complete match statistic. Late entry or data gaps do not allow for complete retroactive statistics. Treat partial statistics visibly as such.
Bombs, grenades, timers and source metadata
| Type | Fields | Interpretation |
|---|---|---|
Vector3State | x, y, z: Double | CS2 world units; in direction a vector, not an angle contract. |
CountdownState | phase: CountdownPhase?, secondsRemaining: double? | Last timer value delivered. No synchronized time service. |
BombState | state: BombStatus?, playerSteamId: string?, position: Vector3State?, secondsRemaining: double? | Current bomb state including possible carrier/action reference. |
GrenadeState | id: string, type: GrenadeKind?, ownerSteamId: string?, position, velocity: vector? , lifetimeSeconds, effectTimeSeconds: Double? | Current object. Do not draw the missing geometry as a zero position. |
flames of a grenade | Array of { id: string, position: Vector3State } or null | Individual flame points of an inferno. |
ProviderState | name: string?, appId: int, version: int?, steamId: string?, timestampUnixSeconds: double? | Source metadata, not a substitute for receivedAt. |
TournamentDraftState | state: DraftStatus?, eventId/stageId: Integer? , eventName/stageName: String? , firstTeam/secondTeam: DraftTeam | GSI draft data. DraftTeam contains id: int? and name: string?. |
A countdown can be graphically updated between two messages, but is not a promise of a common demo tick, a playback speed or reliable pause synchronization. For such a course, the contract does not yet provide a complete time model. After a change of session and outdated data, correct or stop your own timers.
ManagedMatch: permanent identity and series context
managedMatch describes the actively selected match from the library. It doesn’t matter if GSI already delivers a game. Changes to teams, maps or competition create new snapshotre visions without changing the GSI reception time.
| Field | Type | Meaning |
|---|---|---|
id, name | Strings | Managed match identity and display name. |
bestOf | Integers | Series format. |
startsAt | Date? | Scheduled match start with offset. null is not defined. |
teamA, teamB | ManagedTeam | Explicit opponents of the match, regardless of current CT/T sides. |
ct, t | TeamSideResolution | Centrally determined assignment of the current game pages. |
maps | Array from ManagedMatchMap | Ordered game maps and results; standard empty. |
vetos | Array from ManagedVeto | Explicit pick/ban/decider steps; default empty. |
competition | ManagedCompetition? | Associated competition with season, area and sponsors. |
Teams and side resolution
ManagedTeam contains id, name, shortName as strings, logo: string? and seriesWins: int. TeamSideResolution contains side: TeamSide, status: TeamRecognitionStatus, teamId: string? and teamAScore and teamBScore as integers.
The last two numbers are match numbers of unique Steam IDs in the team assignment, not round points. Round points are in game.ct.score and game.t.score. The host distinguishes recognized, inferred, manual, noMatch, tied, conflicting and unknown. Use the delivered teamId; a missing assignment remains open.
Maps to play, veto and results
ManagedMatchMap includes id, name, gameName, number and optionally kind, status, scoreA, scoreB and winnerTeamId. number is the game position from 1. Points always refer to the managed opponents A/B, not to current CT/T. status is pending, live, completed or unknown; older data may contain null.
ManagedVeto contains mapId, name, gameName, type, teamId: string?, number and playNumber: int?. number assigns the veto process; playNumber assigns a played map. A ban is not a played map. From mere list order do not derive a picker, winner or ban.
Competition data
ManagedCompetition contains id, name, seasonId, seasonName, sections, round, logo, sponsors, sectionId and structure. Optional text, logo, sponsorship and structure may be missing. sections is the ordered ancestor path of the assigned area; sectionId denotes the exact assignment.
- A path entry contains
id,nameandkind. - A sponsor includes
id,nameandimageas a local media reference. structure.seasonsincludes all seasons configured withid,competitionIdandname.structure.sectionscontains all areas withid,competitionId,seasonId?,parentId?,nameandkind.
A HUD can display the assigned area and its parents without even guessing the match assignment. A complete tournament tree evaluation or winning logic does not automatically arise from this.
Graphics mapping belongs to the HUD, not the data contract. Use understandable text for unknown identifiers or unavailable files; missing inventory or an absent active weapon must not produce an invented replacement icon. Follow the rights notices for every graphic. The MIT SDK license grants no rights to third-party images.
HLAE radar and killfeed
RadarState contains connectionId: UUID, epoch: long, sequence: long, gameTimeSeconds: double, receivedAt: Zeitpunkt and players: RadarPlayer[]. Each RadarPlayer contains steamId: string, pawnHandle: uint, position, forward and health: int.
Radar complements GSI and does not change its PlayerState.position. The host assigns HLAE players existing GSI identities. Radar with incorrect map, outdated sequence or inappropriate connection is not adopted as a new state. An obsolete radar stock is removed after approximately 750 milliseconds without appropriate update; game.radar can therefore become null again.
Use a fresh HLAE state as a common source for all radar player tokens. A player missing in it does not receive an older GSI position or viewing direction. Only without a usable fresh HLAE stand does the presentation fall back on existing GSI positions. Short linear 50 ms transitions connect successive targets; reset at source/session/map/round/connection/epoch/floor changes. Check freshness against the current reception/render time, not against an outdated own timer value.
Map image and world projection are tasks of the HUD; RadarState does not provide finished image coordinates. When using Boltobserv maps, follow their original calibration data. Its original metadata uses a logical area of resolution * 1024 world units, regardless of the actual PNG measures. Without split, the normalized image component is obtained from x = (worldX + offset.x) / (resolution * 1024) and y = 1 - (worldY + offset.y) / (resolution * 1024). For Nuke and Vertigo, the original height splits apply per player or object: For bottom < z < top, its split offset is additionally applied in percent to X or within the Y reversal. Do not switch the entire composite map based on the Observer and do not invent a missing height. Other map catalogues and their own Overview overrides have their own calibration; the Boltobserv formula may not be transferred to any images.
The visible lap course is also a representation decision of the HUD. Choose suitable game phases and views for the history actually provided. Map series assignment requires exact identity between the actual game map and a prepared series entry. Do not derive a current map from list order or a best-of value alone; get team A/B results regardless of the current CT/T assignment.
A KillFeedEntry contains id: long, connectionId: UUID, epoch: long, gameTimeSeconds: double, killer: KillParticipant?, victim: KillParticipant, assister: KillParticipant?, weapon: string, weaponKind: WeaponKind and the Boolean modifiers flashAssist, headshot, wallbang, attackerBlind, throughSmoke and noScope.
KillParticipant has entityIndex: int, steamId: string?, name: string? and side: TeamSide?. For event identity, use the combination of connection, epoch, and ID; a single numeric counter is not unique about restarts. The stock is limited to a maximum of 32 entries and is reset at relevant session/round transitions.
A repeated snapshot with the same entries is not a new kill. The stock is neither a complete match history nor a general event interface for damage, shots or bombing actions. From a change of HP to zero generate no additional kill.
Player and killer source names may contain HTML entities. Decode such GSI/HLAE display names exactly once and continue to render the result as ordinary escaped text. No source name may be used as HTML. Profile names and own panel texts are not changed in a blanket way. Weapons/equipment icons supplied are only used to display the identifier actually supplied; unknown weapons are not declared by a prefix to a known identity.
Important enum values in JSON
| Type | Values |
|---|---|
DataFreshness | unknown, waiting, live, stale, disconnected |
FeedConnectionStatus | unknown, connecting, connected, disconnected; distinguishable from the SDK connection states. |
TeamSide | unknown, counterTerrorists, terrorists, spectator |
MatchPhase | unknown, warmup, live, intermission, gameOver |
RoundPhase | unknown, freezeTime, live, over |
PlayerActivity | unknown, menu, playing, textInput |
GameMode | unknown, competitive, casual, deathmatch, wingman, armsRace, demolition, custom, cooperative, skirmish, training, scrimCompetitive5v5 |
WeaponCategory | unknown, knife, pistol, submachineGun, rifle, sniperRifle, shotgun, machineGun, grenade, bomb, equipment, stackableItem, tablet, fists, breachCharge, melee |
WeaponStatus | unknown, active, holstered, reloading |
BombStatus | unknown, carried, dropped, planting, planted, defusing, defused, exploded |
CountdownPhase | unknown, warmup, freezeTime, live, over, bomb, defuse, timeoutCounterTerrorists, timeoutTerrorists, paused, gameOver |
GrenadeKind | unknown, smoke, flashbang, highExplosive, molotov, incendiary, decoy, inferno, firebomb |
RoundWinReason | unknown, elimination, bombExploded, bombDefused, timeExpired, hostageRescued |
DraftStatus | unknown, live |
GsiSection | unknown, provider, map, round, player, allPlayers, phaseCountdowns, bomb, grenades, tournamentDraft |
TeamRecognitionStatus | unknown, noMatch, tied, conflicting, recognized, inferred, manual |
MapDecisionKind | unknown, pick, ban, decider |
MapResultStatus | unknown, pending, live, completed |
CompetitionSectionKind | unknown, region, division, phase, group, round, other |
WeaponKind and MapKind contain the specific known catalogue of contents, for example ak47, awp, m4A1S and mirage, nuke and ancient. The complete lists are in the type reference for WeaponKind and MapKind as well as in the included contracts.d.ts. Use the values provided with your SDK and keep the fallback unknown; a new CS2 identifier must not crash the display.
Limitations that a consumer needs to consider
- Snapshots can skip intermediate states; they are not guaranteed seamless event recording.
- There is no full joint demo time contract and no typed camera contract with FOV and Observer mode.
- Player cameras and live video images are not GSI fields and are not supplied by this API.
- Recorded statistics can only cover the observed periods; observe
coversWholeMatch. - Unknown teams, missing media and unavailable geometry remain missing. Do not confuse data availability with a closed zero or default value.
- HUD browser session and common origin are a trust model for local packages, not isolation between any foreign code.
The supplementary Panel reference describes the separate control channel. For existing foreign HUDs, the LHM/JT compatibility profile also applies.