ReplayLabs Developer API
Public integration documentation
This page documents the current public ReplayLabs API exactly as it is exposed today. It covers authentication, plans, limits, endpoints, payloads, and error behavior.
Base path
/api/external/v1The API is read-only and currently exposes replay retrieval and replay-group membership retrieval.
Starter
EUR 3.99 / month
Tax not included
1 active API key
60 requests / minute
25,000 requests / month
Pro
EUR 9.99 / month
Tax not included
3 active API keys
180 requests / minute
150,000 requests / month
Authentication and scopes
Use a bearer API key
API keys are created in the developer portal. They are sent in the `Authorization` header and are tied to the owning ReplayLabs account.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.replaylabs.app/api/external/v1/replays/227Current scopes:
replays:readgroups:read
Endpoint reference
GET
/api/external/v1/replays/:gameIdRequired scopes: `replays:read`
Returns exactly one replay payload wrapped as `{ data: ... }`.
`:gameId` must parse as a base-10 integer or the API returns `400 BAD_REQUEST`.
A valid active key can currently fetch any existing replay id.
The replay payload always contains both `blue` and `orange` team objects, even if one side has no players.
GET
/api/external/v1/groups/:groupId/replaysRequired scopes: `groups:read`
Returns exactly one group payload wrapped as `{ data: ... }`.
Supported `view` values are `summary` and `ids`.
If `view` is omitted, the API uses `summary`.
Replay rows are ordered by `date DESC NULLS LAST, game_id DESC`.
Response contract
Current behavior and defaults
Replay behavior
`title` currently mirrors `replay_name` because the service reads both from the same stored value.
`created` is the earliest `parse_replay` task creation time found for that replay, not the replay match timestamp.
`playlist` is sourced from `game_entries.gamemode` exactly as stored.
`team_size` is nullable. `duration` is returned as a number and falls back to `0` if missing.
`groups` is ordered by group id ascending.
`blue.players` and `orange.players` are split from stored `team` values `0` and `1`.
Within each team, players are ordered by `last_known_username NULLS LAST`, then `player_id` ascending.
Nullability and defaults
The top-level response wrapper is always `{ data: ... }` on success.
If replay dates or upload timestamps are missing, `date` and `created` are returned as `null`.
Per-player boost and field sections are always present. Missing database rows become numeric zeroes.
Per-player grading is always present. Missing grading data becomes `null` for numeric grade fields, empty objects for `categories`, and empty arrays for `top_strengths` and `top_issues`.
Team stats are always present and are derived from the player payloads returned in that same response.
Replay response
Current payload shape
{
"data": {
"id": 227,
"link": "https://api.replaylabs.app/api/external/v1/replays/227",
"web_url": "https://app.replaylabs.app/match/227",
"created": "2026-03-31T12:14:22Z",
"title": "scrim game 2",
"replay_name": "scrim_game_2",
"replay_id": "CE3BF1B611F125E4BC11DC9AAF19E2AD",
"playlist": "Private",
"team_size": 3,
"duration": 323,
"date": "2026-03-30T19:15:00Z",
"map_name": "ChampionsField_Day",
"groups": [
{
"id": 22,
"name": "Scrim block A",
"link": "https://api.replaylabs.app/api/external/v1/groups/22/replays",
"web_url": "https://app.replaylabs.app/groups/22"
}
],
"blue": {
"name": "Blue",
"players": [
{
"name": "Player 1",
"id": {
"platform": "steam",
"id": "76561190000000001"
},
"stats": {
"core": {
"score": 512,
"goals": 2,
"assists": 1,
"saves": 3,
"shots": 5
},
"boost": {
"amount_collected": 2713,
"amount_used": 2289,
"collected_per_minute": 503.9,
"used_per_minute": 425.4,
"small_pad_count": 55,
"big_pad_count": 29
},
"field": {
"avg_speed": 1540,
"total_distance": 578827,
"avg_distance_to_ball": 2865,
"avg_distance_to_teammates": 3567
},
"demo": {
"inflicted": 1,
"taken": 0
},
"bump": {
"inflicted": 4,
"taken": 2
}
},
"grading": {
"raw_score": 7.18,
"normalized_score": 3.2,
"event_score": 9.45,
"mistake_score": 2.27,
"grading_version": "v1",
"categories": {
"events": {
"mechanics": 2.35,
"decision": 1.1
},
"mistakes": {
"positioning": 0.8,
"boost": 0.3
}
},
"top_strengths": ["mechanics", "decision"],
"top_issues": ["positioning", "boost"]
}
}
],
"stats": {
"core": {
"score": 990,
"goals": 1,
"assists": 1,
"saves": 5,
"shots": 10
}
}
},
"orange": {
"name": "Orange",
"players": [],
"stats": {}
}
}
}Top-level replay fields
`id`
`link`
`web_url`
`created`
`title`
`replay_name`
`replay_id`
`playlist`
`team_size`
`duration`
`date`
`map_name`
`groups`
`blue`
`orange`
Per-player identity
`name`
`id.platform`
`id.id`
Per-player stats sections
`stats.core`
`stats.boost`
`stats.field`
`stats.demo`
`stats.bump`
`grading`
Player and team stats
Field-level reference
`stats.core`
`score`
`goals`
`assists`
`saves`
`shots`
`stats.boost`
`amount_collected`
`amount_used`
`collected_per_minute`
`used_per_minute`
`wasted_supersonic`
`overfill`
`small_pad_count`
`big_pad_count`
`time_0_boost`
`time_100_boost`
`time_0_25`
`time_25_50`
`time_50_75`
`time_75_100`
`stats.field`
`avg_speed`
`total_distance`
`avg_distance_to_ball`
`avg_distance_to_teammates`
`time_supersonic`
`time_boost_speed`
`time_slow_speed`
`time_ground`
`time_low_air`
`time_high_air`
`time_defensive_half`
`time_offensive_half`
`time_defensive_third`
`time_neutral_third`
`time_offensive_third`
`time_behind_ball`
`time_in_front_of_ball`
`powerslide_time`
`powerslide_average`
`powerslide_count`
`stats.demo` and `stats.bump`
`inflicted`
`taken`
`grading`
`raw_score`
`normalized_score`
`event_score`
`mistake_score`
`grading_version`
`categories.events`
`categories.mistakes`
`top_strengths`
`top_issues`
Notes:
`amount_collected` and `amount_used` are derived from stored per-minute values and replay duration.
Team `boost.collected_per_minute` and `boost.used_per_minute` are sums across returned players, not team-wide averages.
Team `field.avg_speed`, `field.avg_distance_to_ball`, `field.avg_distance_to_teammates`, and `field.powerslide_average` are averages across returned players.
Other team field and boost metrics are sums across returned players.
`grading` intentionally excludes the raw internal event timeline and detector internals.
`top_strengths` and `top_issues` are derived from category totals, not from a raw event export.
Group response
Summary view and ids view
`view=summary`
{
"data": {
"id": 22,
"name": "Scrim block A",
"link": "https://api.replaylabs.app/api/external/v1/groups/22/replays",
"web_url": "https://app.replaylabs.app/groups/22",
"replay_count": 3,
"replays": [
{
"id": 220,
"link": "https://api.replaylabs.app/api/external/v1/replays/220",
"web_url": "https://app.replaylabs.app/match/220",
"date": "2026-03-28T18:02:00Z",
"title": "series game 1",
"replay_name": "series_game_1",
"replay_id": "8BE4CE464D5A8EB097E472834941DF59",
"playlist": "Private",
"map_name": "DFH Stadium"
}
]
}
}`view=ids`
{
"data": {
"id": 22,
"name": "Scrim block A",
"link": "https://api.replaylabs.app/api/external/v1/groups/22/replays",
"web_url": "https://app.replaylabs.app/groups/22",
"replay_count": 3,
"replay_ids": [220, 221, 219]
}
}Errors and enforcement
Current error codes:
`BAD_REQUEST`
`UNAUTHORIZED`
`FORBIDDEN`
`NOT_FOUND`
`INTERNAL_ERROR`
`PLAN_INACTIVE`
`MONTHLY_QUOTA_EXCEEDED`
Monthly quota error body
{
"error": {
"code": "MONTHLY_QUOTA_EXCEEDED",
"message": "Monthly API request quota reached for this plan",
"limit": 25000,
"used": 25000,
"period_start": "2026-04-01"
}
}Per-minute rate-limit error body
{
"error": "Too many API requests for this plan. Please slow down.",
"limit": 60,
"remaining": 0,
"resetAt": "2026-04-25T12:35:00.000Z"
}All keys are subject to a hard safety cap of 240 requests / minute by default.
Starter and Pro plans enforce their own per-minute caps and monthly quotas.
If a request exceeds a rate limiter, the body uses a plain top-level
error string, not an { error: { ... } } object.If a request exceeds the monthly quota, the body uses the structured
{ error: { ... } } shape shown above.If the owning user no longer has an active API subscription, owned keys stop working.
Developer portal workflow
1. Log in and open the developer portal.
2. Buy `Starter` or `Pro` through Stripe.
3. Create an API key.
4. Store the raw token immediately. It is shown once.
5. Use the token as a bearer key against the external API.
