Fleet Observability

Read what your fleet is actually doing: the live workspace view, the fleet-state, live-summary and matchmaking-stats APIs, and how to judge autoscaler health.

The fleet page in your workspace is a live console for the fleet: what Discovery reports right now, where players are waiting, what needs attention and what scaled. The same live data is available over the API for your own tooling.

The fleet page

Open Hosting > Fleets in the sidebar and click Manage on a fleet (requires fleets.view). The page has six tabs: Overview, Locations, Game servers, Matchmaking, Releases and Settings. Each tab has its own address, so a reload or a shared link opens the same tab. Live data refreshes every 15 seconds while the page is visible, and the line beside the strip says when it last updated.

Three tabs can carry a badge. Overview counts the critical items under Needs attention, Game servers counts member game servers that are absent from Discovery, and Releases reads Running while a release is in progress or Failed while a failed release has not been dismissed.

A control you lack the brand permission for (Pause, Deploy New, Attach Deployment, Policy, detach, Edit on Agent Settings, cancelling or rolling back a release, and Dismiss and reclaim spare servers on a failed release) is shown disabled. Hover it to see which permission it needs and who can grant it.

A fleet with no member deployments shows "This fleet has no member deployments yet." on both the Overview and Locations tabs, with Deploy New and Attach Deployment under it.

Overview tab

The Live now card has four tiles:

  • Live players: the players each game server last reported to Discovery, out of the player slots (total), the player capacity the live game servers advertise, added up.

  • Game servers: every game server Discovery currently sees for this fleet, split by state. ready game servers are idle and allocatable, in session ones are running a match, and draining ones are out of rotation (booting, unhealthy, retiring for a release, or shutting down), so they are not allocatable and players do not see them on the public server list. absent counts member game servers Discovery does not report at all.

  • Ready / target: ready game servers against the target, which is the summed Ready Buffer of all member deployments. Ready at or above the target means the warm buffer is healthy. Ready persistently below it means the autoscaler is still catching up or a location is pinned at its Max. Under it, Desired ready shows the matchmaker's demand hint and how the autoscaler splits it per deployment, or "no hint" when the hint has expired or was never written.

  • Matchmaking queue: the queued tickets, the oldest wait, the allocations pending, and how many tickets are unconstrained (they set no latency ceiling, so any location can serve them).

The most common under-capacity signal is a rising queue with no ready game servers anywhere: raise Ready Buffer or Max on the relevant locations, or have your matchmaker write a higher desired-capacity hint.

Workspace capacity shows your workspace's resource limits across every fleet, for Memory, CPU (in cores) and Game servers. Each row shows what is in use against the limit, or "no limit". A striped part of the bar shows what is reserved by fleet peaks: what your fleet deployments may still add before they reach their Max. A policy raise that would not fit beside those reservations is refused, and when a limit runs out, scale-ups are blocked.

Needs attention lists what is wrong right now, worst first, as Critical, Warning or Info. Each item says what to do next and has a button that opens the tab showing it, already filtered. You can see:

  • A scale-up blocked by a workspace resource limit (critical). The autoscaler tries again when its backoff ends.

  • Players queued for a location where this fleet has no deployment. It is critical when no location this fleet serves can take those tickets, or when the matchmaker wants game servers there. It is info when the players can still be placed elsewhere, or when another fleet on the same Discovery app hosts that location.

  • A location with no ready game servers while it has a ready target or a queue (critical).

  • Member game servers older than 15 minutes that are not reporting to Discovery (warning, or critical when the deployment's absence alert is on and scale-up is held).

  • Game servers reporting a build other than the one their deployment is pinned to (warning). This is expected during a release.

  • Game servers holding more players than their players capacity, so backfill into them is refused (warning).

  • Game servers on the fleet's Discovery app that belong to another fleet, or to no fleet at all (info), with an Open Discovery app button. Game servers in no fleet are usually self-hosted game servers sending heartbeats with the same app's tokens, or deleted game servers that have not yet aged out. They are listed for players, but no fleet scales them. A persistent entry means something outside your fleets is using the linked app.

Scaling activity lists the fleet's scale-ups and scale-downs of the last 6 hours, newest 20. Each row has a Source badge saying what started it: Autoscaler (keeping the ready buffer, or replacing game servers it proved dead), Release (a spare game server while others update, or reclaiming that spare) or Manual (a person or a script through the scale endpoint). The Change column reads Scaled up, Replaced, Blocked or Scaled down, and Game servers shows how many were requested and added or removed. The job number opens the job page, which needs brand.jobs.view.

The Trends card at the bottom of the tab charts the fleet over the last hour or day. Pick 1 h or 24 h; the choice is kept in the page address. The charts are:

  • Game servers: Ready, In session and Draining, against the Ready target.

  • Live players: Live players against Player slots (total).

  • Matchmaking queue: Queued tickets and Unconstrained tickets.

  • Oldest wait: the longest time a queued ticket has waited.

  • Allocation outcomes: Allocated, No capacity and Agent unavailable.

  • Refused requests: Rate limited, Auth refused and Heartbeats over a cap.

  • Game server drop-outs: Became unverified, Heartbeats stopped and Agent disconnects.

Vertical markers show Scale-up blocked, Release failed, Release started, Release completed, Deploy, Replacement, Scale up and Scale down. Hover a point to see its values and the events at that time.

At 1 h there is one point per minute. At 24 h there is one point per 5 minutes: game servers and players are averaged, counts are added up, and the longest wait is kept. A break in a line means no sample was recorded for that point, never a zero. The workspace keeps 24 hours.

The last three charts come from Discovery's counters, so they appear only when the fleet is the only fleet on its Discovery app and Discovery reports counters. Otherwise one line headed Allocation outcomes, refused requests and game server drop-outs stands in for them and says why:

  • This Discovery app serves more than one fleet, so outcomes can't be split per fleet.

  • Needs an Allocate- or Both-scope token.

  • Discovery hasn't reported counters yet.

A new fleet reads Trends appear after the first few minutes of recording; the workspace keeps 24 hours. until enough has been recorded.

Locations tab

The Locations tab has one row per location, with Ready / target, In session, Draining, Absent, Live players, Queue and Desired. Several deployments in the same location share one row, and their location names are listed under it. A ticket counts under every location its latency ceiling accepts, so the Queue column does not add up to the total. Two other kinds of row can appear:

  • No deployment: players are asking for this location and this fleet has no deployment there. Only Queue and Desired are filled in.

  • No location: member deployments whose location has no zone Discovery knows.

Click a row to open its member deployments. Each one shows its Build, its game servers with live Ready, In session, Draining and Absent badges, and its Min, Max, Ready Buffer and Recycle policy. Under the badges, a deployment shows:

  • The players capacity its game servers advertise and where it comes from, for example 16 players (deployment player count) or 32 players (agent settings). A warning appears when a game server already holds more players than that capacity, because every backfill onto it is refused.

  • Scale-up blocked by ... when adding game servers failed on a workspace resource limit, with the limit and its usage. The autoscaler retries after about 10 minutes.

  • An alert when at least half of the deployment's live game servers are missing from Discovery, for example 3 of 4 live game servers are absent from Discovery, with the likely causes. Scale-up beyond Min is held while it shows. See How the autoscaler decides.

Under the deployments, an open row also shows Trends for this location: the Game servers and Live players charts for that location alone, with markers for its deployments and for releases, and the same 1 h / 24 h choice.

Policy edits the scaling and recycle policy in the row (requires fleets.manage). Deploy New and Attach Deployment are at the top of the tab. Under the table, a line counts the unconstrained tickets and any tickets that name only locations PingCore does not host. If that second count is not zero, check the location ids your game sends.

While live data is unavailable, the tab lists the member deployments without the location rows, so you can still edit a policy.

Game servers tab

The Game servers tab lists the fleet's member game servers, 50 per page, with Previous and Next under the table. Its columns are Game server, State, Location, Deployment, Build, Live players, Endpoint and Last heartbeat. An absent game server reads Not reporting. The game server number opens its manage page (console, files and player history), and the deployment name opens the deployment. Both need brand.servers.view.

Above the table you can search by id, address, deployment, location or build, pick a state (each with its count, including absent), and narrow the list with the Location, Deployment and Build selects. Each choice counts the game servers the other filters match, and the other choices stay available while a filter is set, so you can switch one value for another directly. The sort select orders by game server id, most live players, latest heartbeat or state. The filters are part of the page address, so you can share a filtered view.

Matchmaking tab

The Matchmaking tab shows the Matchmaking card of the fleet's Discovery app, described under Matchmaking card. Matchmaking is Discovery data, so this tab needs the View Discovery brand permission (discovery.view) as well as View Fleets (fleets.view). Without it, the tab names the missing permission and who can grant it.

Releases and Settings tabs

The Releases tab holds the release panel, described in Shipping an Update.

The Settings tab shows the fleet's Discovery app and its Discovery public ID, the fleet's status with Pause or Resume, and the Agent Settings card (see the agent configuration).

When live data is unavailable

Any tab can show "Live data unavailable" while the rest of the page keeps working, with the last known state still on screen. That means the live layer could not be read this cycle: the Discovery service is unreachable, the linked app is disabled, the app has no active ingest token, or your own tooling is polling the same app heavily and has used up the app's observability budget for this minute (see Rate limits). The fleet itself is fine; its configuration is intact and the page retries automatically. Do not diagnose the fleet from a live-data outage.

The same applies to the API: the platform's fleet live-state endpoint answers 503 in that situation, and the fleet's configuration endpoints keep answering normally.

Session logs

Every time a session ends on a fleet server, the server writes a compressed slice of its game and application logs for that session and uploads it to PingCore. The slices are also drained before a server is removed, so the logs of a server the autoscaler scaled down or a release replaced are still readable afterwards.

  • Find them in the Session Logs list on the server's own page. When a release stops on a game server, the fleet's Releases tab also shows that game server's Session Logs as a collapsed list under the release. Both require brand.servers.view.

  • Each entry carries the time the session ended, its allocation id and session id when the server reported them, and a download link for the gzip file.

  • A slice holds the tail of the logs, up to 4 MiB uncompressed, and a single uploaded archive may not exceed 8 MiB. A server keeps at most 10 slices locally before the oldest is dropped, and at most 40 MiB of stored archives on the platform.

  • Archives are deleted after 7 days. Your brand can set a different retention between 1 and 30 days.

  • When a server's pod crashed rather than exiting cleanly, PingCore stores the previous container's log tail instead, so the crash is still visible.

Slices are best effort. A server that could not upload before it was removed logs one line and the removal continues, so a missing slice never blocks a scale-down or a release.

Reading live state over the API

For dashboards and alerting you have two options.

Through the platform

GET /api/fleets/{fleetId}/live on the platform API (requires fleets.view) returns the fleet's live state: per-member-deployment lifecycle counts with per-server detail, unmapped servers, app-wide lifecycle state counts, the desired-ready hint, and the matchmaking stats. Discovery credentials never leave the platform; this is the recommended path for workspace tooling.

The unmapped servers in this response also include game servers of another fleet that shares the fleet's Discovery app. The fleet page lists those separately under Needs attention on the Overview tab.

Each member deployment entry also carries the signals the rows show:

  • absent and absentServerIds: how many of the deployment's live game servers Discovery does not report, and which.

  • alert: the absence alert text when at least half of them are absent (scale-up is held), otherwise null.

  • scaleUpBlocked: null, or the active resource-limit block as reason (brand_memory_limit, brand_cpu_limit or brand_server_limit), detail, message, and recordedAt and retryAt in Unix seconds.

Directly from Discovery

Your own backend can read the raw projections from the Discovery service. On a private app any active token of the app works. On an open-registration app you need an Allocate (or Both) token: the in-game Heartbeat token ships inside the game binary, so it gets a 403 with reason: "scope" on these reads.

Fleet state

GET /v1/apps/{publicId}/fleet-state
{
  "error": false,
  "servers": [
    {
      "serverId": "4211",
      "tier": "agent",
      "state": "ready",
      "counters": { "sessions": { "count": 0, "capacity": 1 } },
      "pendingClaims": null,
      "updatedAt": 1756500000000,
      "name": "Fleet 4211",
      "ip": "203.0.113.9",
      "port": 27015,
      "players": 3,
      "maxPlayers": 16,
      "version": "1.0.0",
      "verified": "unverified",
      "lastProbeError": "No UDP echo response before timeout",
      "reason": null,
      "location": "eu-west-ams"
    }
  ],
  "counts": { "total": 1, "ready": 1, "allocated": 0, "inSession": 0, "draining": 0, "external": 0 },
  "desiredReadyServers": 12
}

One entry per live server. tier distinguishes fleet servers (agent) from plain heartbeat servers; state is the lifecycle state (heartbeat-tier servers read external); pendingClaims shows capacity claimed by allocations not yet confirmed; updatedAt is the last state assertion in epoch milliseconds; reason says why a draining server is out of rotation (booting, unhealthy, retiring or shutdown); location is the location id from GET /v1/locations that the server advertises as meta.location, or null when it advertises none or one the platform does not list. Fields are only ever added, never removed or renamed, so a tolerant reader stays compatible across service updates.

verified and lastProbeError report the reachability probe on both tiers. They are null ("not probed") when the app's verification mode is No verification. Otherwise verified is pending until the first probe, then verified or unverified, with the failure in lastProbeError. For a hosted (agent) game server the result is diagnostic only: the example above is a hosted game server that is still listed for players although its game port did not answer the probe. See Reachability Verification.

Paging and filters

Without query parameters, fleet-state returns every live server in the shape above. The parameters below are optional, and an unknown or unusable value is ignored rather than refused.

  • state: comma-separated lifecycle states to keep, for example ready,draining.

  • tier: agent (hosted) or heartbeat (self-hosted).

  • location: one location id, or * for servers in no known location.

  • version: an exact build version.

  • search: a case-insensitive part of the server id or name.

  • fields=core: the lean entry, with only serverId, tier, state, reason, counters, players, maxPlayers, version, updatedAt and location.

The filters and fields=core work with or without paging, and counts always covers the whole app. Send limit (1 to 500) to read one page. With paging you can also send offset and sort: serverId (the default), updatedAt, players, state, version or location, with a - prefix for descending order.

GET /v1/apps/{publicId}/fleet-state?limit=50&state=ready,draining&location=eu-west-ams&sort=-updatedAt

A paged response adds these keys next to servers, counts and desiredReadyServers:

  • byLocation: for each location, the servers per lifecycle state plus players and maxPlayers, across the whole app. Keys are sorted, with * (servers in no known location) last.

  • facets: states, locations and versions, each a list of { value, count }. Each list counts the servers the other filters select, so you can build filter choices from it.

  • totalServers (servers matching the filters before the page is cut), returned, limit, offset, paged: true and fields.

Live summary

GET /v1/apps/{publicId}/live-summary
{
  "error": false,
  "generatedAt": 1756500000000,
  "counts": { "total": 3, "ready": 1, "allocated": 0, "inSession": 2, "draining": 0, "external": 0 },
  "byLocation": {
    "eu-west-ams": { "total": 2, "ready": 1, "allocated": 0, "inSession": 1, "draining": 0, "external": 0, "players": 9, "maxPlayers": 32 },
    "*": { "total": 1, "ready": 0, "allocated": 0, "inSession": 1, "draining": 0, "external": 0, "players": 4, "maxPlayers": 16 }
  },
  "players": 13,
  "maxPlayers": 48,
  "desiredReadyServers": 2,
  "desiredReadyServersByLocation": { "eu-west-ams": 1 },
  "matchmaking": {
    "queuedTickets": 7,
    "oldestWaitSeconds": 41,
    "queuedByLocation": { "eu-west-ams": 5, "us-east-nyc": 3 },
    "unconstrainedTickets": 1,
    "unknownZoneTickets": 0,
    "queuedByZoneSet": [
      { "zones": ["eu-west-ams"], "tickets": 3 },
      { "zones": ["eu-west-ams", "us-east-nyc"], "tickets": 2 },
      { "zones": ["us-east-nyc"], "tickets": 1 }
    ],
    "sampleTruncated": false,
    "pendingAllocations": 1,
    "recentAllocations": [
      { "allocationId": "…", "serverId": "4211", "createdAt": 1756500000000 }
    ]
  },
  "counters": { "epoch": "6f1c2a9e-4b7d-4e2a-9c3f-1d8e5b7a2c40", "startedAt": 1756400000000, "values": { "allocation.ok": { "eu-west-ams": 412 } } }
}

Live summary is the cheap read to poll for a dashboard. It returns counts only: servers per lifecycle state and per location, live players and player slots, the desired-ready hint with its split by location, the queue, and the app's running counters. Add include=servers to also get every live server as a lean entry, the same shape as fields=core on fleet-state.

Every location key is a location id from GET /v1/locations. A meta.location, latency key or pinned location that the platform does not list is counted but never becomes a key. In byLocation, * holds the servers in no known location. In matchmaking:

  • queuedTickets is the exact number of queued tickets.

  • queuedByLocation counts each ticket under every known location it accepts, so the values can add up to more than the queue.

  • unconstrainedTickets counts tickets with no pinned location and no usable latency ceiling (no ceiling, or no latency map), which can be placed anywhere.

  • unknownZoneTickets counts tickets that accept no known location: their latency map or pinned location names only locations the platform does not list, or their pinned location is outside their own latency ceiling. Each counts once.

  • queuedByZoneSet groups tickets by the exact set of known locations they accept, largest group first. It holds at most 50 sets; any further sets are folded into one final entry with zones: null.

  • sampleTruncated is true when the queue was longer than the 500 tickets Discovery scans. queuedByLocation, unconstrainedTickets, unknownZoneTickets, queuedByZoneSet and oldestWaitSeconds then cover only the scanned tickets.

  • oldestWaitSeconds, pendingAllocations and recentAllocations read as they do in matchmaking-stats.

Counters

counters in the live summary holds running totals for the app, for diagnostics and for computing rates between two reads:

"counters": {
  "epoch": "6f1c2a9e-4b7d-4e2a-9c3f-1d8e5b7a2c40",
  "startedAt": 1756400000000,
  "values": {
    "allocation.ok": { "eu-west-ams": 412, "us-east-nyc": 198 },
    "allocation.no_capacity": { "eu-west-ams": 3 },
    "http.rate_limited.observability": { "*": 12 },
    "server.expired": { "*": 7 },
    "agent.disconnect": {}
  }
}

The example is shortened; values always carries every counter name, with {} when nothing was counted. Each name maps a location id from GET /v1/locations, or *, to a total. * holds counts with no known location, and every counter that has no location at all. Locations are in lexical order with * last.

  • allocation.ok, allocation.no_capacity and allocation.unavailable: one per allocation request outcome. Each POST .../allocations that allocated, answered 409 no_capacity, or answered 503 agent_push_unavailable, and each match the first-party matchmaker forms on a tick. A match that waits for capacity counts no_capacity on every tick it is retried. Replays of an idempotent request never count. ok is counted under the allocated game server's location.

  • reservation.ok and reservation.no_seats: seat holds placed and 409 no_seats refusals, on reservation create and quick-join. Replays never count.

  • ingest.self_hosted_cap, ingest.ip_cap and ingest.agent_cap: each attempt to register a new game server that the Self-hosted allowance, the open-registration per-IP limit, or the Concurrent Server Limit for hosted game servers refused. A game server that keeps trying counts on every attempt.

  • http.rate_limited.ingestip, .heartbeat, .allocation, .reservation, .observability, .autoscaler, .playerapp and .player: 429s, one counter per rate limiter of the app. The per-IP limits on the public list and on anonymous token requests are not counted.

  • http.unauthorized and http.forbidden: 401s and 403s on this app, such as a revoked or wrong-scope token or a refused player token. Anyone can send a junk player token to your public ID, so treat these as a diagnostic, not proof that your own callers are misconfigured.

  • verify.became_unverified: a game server that turned unverified. Repeated failed probes count once.

  • server.expired: a game server whose heartbeats or agent stopped, counted once when its record expires.

  • agent.hello and agent.disconnect: a hosted game server's agent connecting, and its connection closing.

Counting is at least once: a batch can occasionally be added twice, and nothing is ever subtracted. Values lag the events by up to 5 seconds. counters is null only when the counters could not be read, and the rest of the summary is still served. Treat null as unknown, never as 0.

To get per-minute figures, keep the previous read and compare:

  • Same epoch: the delta is the later value minus the earlier one. A location key that was missing before counts as 0.

  • A new epoch whose startedAt is later than your previous read: the totals restarted, so the later value is the delta.

  • A value that went down or disappeared under the same epoch, or a new epoch that started no later than your previous read: skip that interval and use the later read as your new baseline.

  • epoch is null: nothing has been counted since the totals were last reset, so every value is 0.

Matchmaking stats

GET /v1/apps/{publicId}/matchmaking-stats
{
  "error": false,
  "queuedTickets": 4,
  "pendingAllocations": 1,
  "desiredReadyServers": 12,
  "desiredReadyServersByLocation": { "eu-west-ams": 8, "us-east-nyc": 3 },
  "recentAllocations": [
    { "allocationId": "…", "serverId": "4211", "createdAt": 1756500000000 }
  ]
}

recentAllocations is newest first, capped at 25, and purely diagnostic. desiredReadyServers is null in all three endpoints when no hint is currently active.

desiredReadyServersByLocation splits the hint by location: how many ready game servers are wanted in each location id from GET /v1/locations, including a location where no game server runs yet. The first-party matchmaker fills it whenever it writes the hint. Matches with no latency map and no pinned location count only in desiredReadyServers, so the values add up to at most that number. Locations with no demand are left out. It is null exactly when desiredReadyServers is null, and a hint you write yourself with PUT .../desired-capacity has no split and clears it.

Rate limits

Fleet-state, matchmaking-stats and live-summary spend the app's observability budget: 240 requests per minute by default, shared with the three matchmaking inspector reads described below. They never spend the allocation budget, so polling them does not compete with your matchmaker's allocation traffic. The Backend requests budget shown on the app page does not apply to them either. The workspace's fleet and Discovery app pages and the MCP Discovery tools read through the same observability budget, so leave headroom when you poll.

Every fleet read that passes authentication answers with an X-PingCore-Budget header naming the budget it was charged to, including a 429. Your calls read observability; autoscaler is reserved for PingCore's own autoscaling reads. A 429 carries Retry-After.

Discovery app live view

Every Discovery app page has two tabs, Live and Setup, each with its own address. A new app opens on Setup, where you issue a token and send a test heartbeat (Try it: get a test game server listed in the Connect your game card). Opening an app from the list goes to Live when a game server is live in it now, or has been seen live since the workspace began recording trends. It goes to Setup when the app has never been seen live, or when it is disabled, has no active token, or is not on Discovery yet. When the app serves a fleet, a banner at the top links to that fleet's page for its locations, exceptions and scaling activity.

The Live tab refreshes every 15 seconds while visible. It has five cards:

  • Live now: the app-wide tiles Live players (out of the player slots, total), Game servers by state, Ready with the Desired ready hint, and Matchmaking queue.

  • Locations: one row per location with Game servers, Ready, In session, Draining, Live players, Queue and Desired. No location collects self-hosted or external game servers that report no location PingCore hosts.

  • Trends: the same card as on a fleet's Overview tab (see Trends), for the whole app, without the Ready target and without markers.

  • Live game servers: the app's live game servers, described below.

  • Matchmaking: see Matchmaking card.

When the app has no active token or is disabled, the tab says so and links to the Setup tab, where you fix it. A new or just-enabled app can say for about a minute that Discovery has not picked it up yet; the tab retries by itself.

Open-registration apps with only Heartbeat tokens

An open-registration app's Heartbeat token ships inside your game, so it cannot read the app's full live state. When such an app has no active Allocate- or Both-scope token, the Live tab shows Discovery's public server list instead:

  • A note headed Listed game servers (public list) says that draining game servers, unverified game servers and the matchmaking queue are not shown, and links to Heartbeat Tokens on the Setup tab. Issue an Allocate- or Both-scope token there to unlock the full view.

  • Live players and the player slots are real numbers. Ready, In session, Draining, the queue and Desired ready show a dash.

  • Locations rows come from the location each listed game server reports.

  • Trends draws only Listed game servers and Live players against Player slots (total). One line in place of the other charts reads Matchmaking queue, allocation outcomes, refused requests and game server drop-outs: Needs an Allocate- or Both-scope token.

  • The game server card is titled Listed game servers (public list). It has a Location filter only, searches by name only, and has no Verified column, because every listed game server on an open app has passed its probe.

  • The Matchmaking card shows an information line asking for an Allocate- or Both-scope token instead of an error.

  • The Live tab's badge reads "N listed" instead of "N live", and the count on the Setup tab reads Listed game servers: N right now (public list; draining and unverified game servers are not counted).

The Live game servers card shows 50 game servers per page, with Previous and Next under it. Its columns are Name, Server ID, Tier, State, Location, Build, Live players, Endpoint, Verified (hidden when the app's verification mode is No verification) and Last heartbeat. When nothing is live, the card links to the test heartbeat in the Connect your game card on the Setup tab.

Above the table you can search by name or server ID and narrow the list with the State, Tier (Hosted or External) and Location selects. Each State and Location choice shows how many game servers the other filters match, and the other choices stay available while a filter is set. No location collects game servers that report no location the platform knows. When the Discovery service is too old to filter, the card hides the search and selects, reads Filters need a newer Discovery service, and shows the first 50 game servers unfiltered.

For heartbeat-only integrations that is usually all the observability you need; the heartbeat response's verified and lastProbeError fields cover per-server diagnosis, as described in Reachability Verification.

Matchmaking card

The Matchmaking card sits under Live game servers on the app's Live tab (requires discovery.view). The fleet's Matchmaking tab shows the same card for the fleet's Discovery app, and needs both fleets.view and discovery.view. The card refreshes every 15 seconds while visible. Its header badge counts the tickets currently queued. It has three tables and, when any exist, a fourth.

  • Queues: one row per queue with queued tickets. Queued is the ticket count, By location shows how many of them accept each location as chips (eu-west-ams: 3), Join-in-progress counts tickets that opted into backfill, Oldest wait is the longest wait in the queue, and Joinable sessions and Open seats summarise the joinable records published for that queue.

  • Joinable sessions (shown when any record is live): Server, Queue, Open seats as effective / published (the effective figure is the published openSeats minus joiners already matched but unconfirmed minus seats held by reservations on the same server; the matcher sells against the effective figure), Pending joiners, Publisher (backend for the HTTP route, agent for a hosted server's own record), Allocatable, Delivered to agent, and Expires.

  • Tickets: the queued tickets, oldest first, at most 100. Party and Session (partySize and minSessionSize..sessionSize), Owner (Backend, or Player followed by a 12-character hash of the player id; the raw id never reaches the workspace and a ticket's context is never shown), Waiting, Flags (relaxed once the ticket's relaxation delay has elapsed, join-in-progress), Attributes as key=value chips, and Best latency (the lowest entry in the ticket's latency map with its location).

  • Recent matches: matches formed in the last 24 hours, newest first, at most 50. Players as players / sessionSize, Tickets, Location, Server, Allocation, Flags (relaxed, backfill), and Wait (how long the oldest ticket in the match waited). Matches older than 24 hours drop off on their own; there is no longer history.

By location is the counter to read when constrained tickets sit queued. A ticket with a maxLatencyMs ceiling counts once under every location it accepts; a ticket with no ceiling counts under the Unconstrained chip, because it can be placed anywhere. A location with queued tickets but no ready servers is a fleet capacity gap: the desired-capacity hint the matchmaker writes is one number for the whole app and cannot say which zone is short, so this table is where you see it. The fleet's Locations tab shows the same queue per location beside each location's ready game servers, and that is where you fix it: raise Min (or Ready Buffer) on the member deployment in that location.

Delivered to agent has three readings:

  • yes: backfills pushed to this server are being acknowledged by its PingCore agent.

  • A dash: no backfill has been pushed to this server since the record was published, so nothing can be said yet.

  • no: a backfill was pushed and has not been acknowledged. Either the server has no PingCore agent (an HTTP-published record on a server you host elsewhere: poll GET .../joinable-session or GET .../allocations/{id} to learn the joiners), or it runs an older game supervisor that does not deliver backfills to the game (move it to a current image; see Supervisor Base Images). On a current hosted game server the game reads its joiners from GET /v1/backfills on the local SDK; see Making Your Game Fleet-Ready. When any row reads no, a warning under the table repeats this and links to the joinable-sessions section of Matchmaking Integration.

When nothing is queued and no match has formed in 24 hours, the card shows one pre-filled ticket curl for your app (masked token on screen, the real one on copy, when an Allocate-scope token was issued in this page session) that demonstrates a relaxed match. The command appears once the Discovery base URL is known. Run it twice with different ticket ids and the match forms on the next tick once a fleet server is ready.

The card reads Live data unavailable when the platform could not reach the Discovery service this cycle, and retries automatically; the app's configuration is unaffected. The heartbeat token of an open-registration app ships inside the game binary, so it cannot read the queue; the platform uses the app's strongest token (Allocate or Both first) to read the card. When an open-registration app has no Allocate- or Both-scope token, the card shows an information line instead: Matchmaking needs an Allocate- or Both-scope token on an open-registration app: its heartbeat token ships inside the game, so it cannot read the queue. It links to Heartbeat Tokens on the Setup tab, where you issue one to see queues, tickets and recent matches. With no active token at all the card reads Matchmaking cannot be read: this app has no active token. Issue one in the Heartbeat Tokens section of the app's Setup tab and this card starts working automatically. It also points to Heartbeat Tokens on the app's Setup tab.

The card reads three Discovery endpoints your own tooling may call too, each answering generatedAt in epoch milliseconds:

  • GET /v1/apps/{publicId}/matchmaking/queues: the queue rows (queue, queuedTickets, joinInProgressTickets, oldestWaitSeconds, joinableSessions, openSeats, queuedByLocation keyed by location with "*" for tickets with no ceiling), totalQueuedTickets, truncated, and the joinableSessions rows (serverId, queue, openSeats, effectiveOpenSeats, pendingJoiners, sessionSize, publisher, allocatable, deliveredToAgent as true, false, or null, location, publishedAt, expiresAt, and lastBackfillRefusal). lastBackfillRefusal is null, or { reason, at } naming why the last backfill onto that record did not go through (counter_full, server_not_in_session, seats_unavailable, no_capacity, or agent_push_unavailable); a republish clears it. The reasons are explained under Troubleshooting on Matchmaking Integration.

  • GET /v1/apps/{publicId}/matchmaking/tickets?queue=ranked&limit=100: the redacted ticket rows (ticketId, queue, sessionSize, minSessionSize, relaxAfterSeconds, partySize, joinInProgress, ownerKind, playerIdHash, waitSeconds, relaxed, attributes, rules, latency, maxLatencyMs, filters, createdAt), oldest first. limit is 1 to 100; queue is an optional exact filter. Ticket context is never included.

  • GET /v1/apps/{publicId}/matchmaking/matches?limit=50: the recent matches (matchedAt, queue, sessionSize, players, tickets, relaxed, backfill, location, serverId, allocationId, anchorWaitSeconds, maxWaitSeconds), newest first, with no ticket ids or player data.

On a private app any active token of the app reads them; on an open-registration app only an Allocate (or Both) scope token does, and a heartbeat token answers 403 with reason: "scope". The three routes spend the app's observability budget, 240 requests per minute by default, which fleet-state, matchmaking-stats and live-summary also spend. It is separate from the allocation budget, so polling the inspector never competes with your matchmaker's allocation traffic.