Replay Parser

Upload a Fortnite .replay file and receive structured match data — metadata, the reporter's stats, per-player aggregates, the ordered elimination feed, participants, and real per-team placements decoded from the replay's network stream.

Endpoint

POST/replay/parse

Base URL https://api.zyron.pro/api/v1 — so the full URL is https://api.zyron.pro/api/v1/replay/parse.

Request

Send the replay as multipart/form-data with a single field named replay containing the .replay file.

Headers

HeaderRequiredValue
AuthorizationRequiredBearer zyr_your_key
Content-TypeSet by clientmultipart/form-data (with boundary — let your HTTP client set it)

Body field

FieldTypeDescription
replayrequiredfileThe Fortnite .replay file. Max 35 MB. Must be a valid Unreal replay — validated by the magic bytes 0x1CA2E27F at the start of the file.

Constraints

Examples

curl -X POST https://api.zyron.pro/api/v1/replay/parse \
  -H "Authorization: Bearer zyr_your_key" \
  -F "replay=@match.replay"

Response

A 200 OK returns a JSON object with these top-level keys: source, meta, reporter, reporterStats, players, elims, participants, chunkSummary, and placements.

FieldTypeDescription
source"native" | "js""native" when the replay's network stream was decoded (real per-team placements, reporter.epicId, per-player teamIndex/placement). "js" means the network decoder was unavailable (e.g. a brand-new game build it doesn't yet support) and the response fell back to the event-chunk parser, in which case placements is the estimated heuristic described below.

meta — object

FieldTypeDescription
startedAtstringMatch start time, ISO 8601.
lengthMsnumberMatch length in milliseconds.
buildstring | nullGame build string, e.g. ++Fortnite+Release-41.00. May be null if absent from the replay.
mapPathstring | nullGame asset path of the map, when present in the replay.
playliststring | nullPlaylist id, e.g. Playlist_Habanero_PunchBerry_Duos (native source only).
teamsnumber | nullNumber of teams in the lobby (native source only).
maxPlayersnumber | nullLobby capacity (native source only).
friendlyNamestringThe replay's internal name.
fileVersionnumberReplay container format version.
networkVersionnumberEngine network version the replay was recorded with.
changelistnumberEngine changelist the replay was recorded with.
encryptedbooleanWhether the replay's event chunks are AES-encrypted (nearly always true).
elimParseFailuresnumberElimination events that could not be decoded — usually 0.

reporter — object

The uploader / recording player. Ground truth, not an estimate.

FieldTypeDescription
epicIdstring | nullThe uploader's Epic account ID (native source; null if the replay didn't flag the recording player).
placementnumber | nullFinal placement in the match.
totalPlayersnumber | nullLobby size for the match.
teamIndexnumber | nullThe uploader's team index (native source only).
eliminationsnumberThe uploader's kill count.
wonbooleanplacement === 1.

reporterStats — object | null

The uploader's full stat block for the match, straight from the replay's AthenaMatchStats event. null when that event wasn't present.

FieldTypeDescription
accuracynumber0–1.
assistsnumberAssist count.
eliminationsnumberSame value as reporter.eliminations.
weaponDamagenumberDamage dealt with weapons.
otherDamagenumberDamage dealt by other means (falls, storm, etc.).
revivesnumberTeammates revived.
damageTakennumberDamage received.
damageToStructuresnumberDamage dealt to builds.
materialsGatherednumberMaterials harvested.
materialsUsednumberMaterials spent building.
totalTravelednumberDistance travelled, in centimetres.

players — array of objects

Per-player aggregates. When source is "native" this is the full lobby roster (every real player, even those never seen in the elimination feed), each carrying its real teamIndex and placement; the kill/knock/death/weapon/position detail is still derived from the elimination feed. Every bot elimination is merged into a single synthetic entry with isBot: true and id: "".

FieldTypeDescription
idstringEpic account ID, or "" for the bots entry.
isBotboolean
namestring | nullDisplay name, when known (native source).
teamIndexnumber | nullReal team index (native source only).
placementnumber | nullReal final placement of this player's team (native source only).
killsnumberFull eliminations credited to this player.
knocksnumberKnocks (down-but-not-out) credited to this player.
deathsnumberTimes this player was eliminated (knocked or killed).
weaponsobjectKills/knocks by this player, keyed by weapon label.
positionsarrayWorld-space points (x/y/z in UE centimetres) tagged with t and event (kill/knock/death) — only the moments this player appears in the elimination feed, not a full movement trail.
killedByarrayEach time this player died: t, by (Epic ID), weapon, knocked.
firstSeennumber | nullTimestamp (ms) of this player's first elim-feed appearance.
lastSeennumber | nullTimestamp (ms) of this player's last elim-feed appearance.

elims — array of objects

Every elimination and knock, sorted by time.

FieldTypeDescription
tnumberMilliseconds since match start.
eliminatorstringEpic account ID of the eliminator, or "" if a bot.
eliminatedstringEpic account ID of the eliminated player, or "" if a bot.
eliminatorIsBotbooleanWhether the eliminator was a bot.
eliminatedIsBotbooleanWhether the eliminated player was a bot.
eliminatorNamestring | nullDisplay name, only ever populated for a named bot — never for a real player.
eliminatedNamestring | nullDisplay name, only ever populated for a named bot — never for a real player.
gunTypenumberRaw weapon/death-cause byte from the replay.
gunLabelstringBest-effort human label for gunType (e.g. AR, Sniper, Storm), or unknown (0x..) for an unmapped byte.
knockedbooleanTrue for a knock (down-but-not-out), false for a full elimination.
eliminatedPosobject | nullWorld-space location of the eliminated player (UE centimetres), when present.
eliminatorPosobject | nullWorld-space location of the eliminator, when present.

participants — array of strings

FieldTypeDescription
participants[]stringDistinct Epic account IDs seen in the elimination feed.

chunkSummary — object

Counts and byte totals for each chunk type in the file — a transparency indicator for how much of the replay this parser actually decodes.

FieldTypeDescription
header{ count, bytes }The single header chunk.
data{ count, bytes }Oodle-compressed netcode chunks — not decoded by this parser.
checkpoint{ count, bytes }Oodle-compressed checkpoint chunks — not decoded by this parser.
event{ count, bytes }Event chunks — this is what elims/reporterStats/players are built from.

placements

Real per-team placements decoded from the replay's network stream (source: "native"): an object keyed by the team's real finishing position (as a string, per JSON), each with its actual roster of Epic IDs — one for solos, up to the squad size.

What a single replay can and can't show

Fallback: source === "js"

FieldTypeDescription
placements[n].winnerbooleanTrue for the match winner(s) — placement 1 (present only if the recorder's team won or the winner was still visible).
placements[n].epicIdsstring[]Epic account IDs on this placement's team (1 for solos, up to the squad size).

Example response

json
{
  "source": "native",
  "meta": {
    "startedAt": "2026-06-22T10:33:48.000Z",
    "lengthMs": 592354,
    "build": "++Fortnite+Release-41.00",
    "mapPath": "/Game/Athena/Maps/Athena_Terrain",
    "playlist": "Playlist_Habanero_PunchBerry_Duos",
    "teams": 20,
    "maxPlayers": 40,
    "friendlyName": "UnsavedReplay-2026.06.22-10.33.48",
    "fileVersion": 8,
    "networkVersion": 33,
    "changelist": 12345678,
    "encrypted": true,
    "elimParseFailures": 0
  },
  "reporter": {
    "epicId": "df05386e908940c4a56c748adf7771a9",
    "placement": 7,
    "totalPlayers": 40,
    "teamIndex": 21,
    "eliminations": 5,
    "won": false
  },
  "reporterStats": {
    "version": 3,
    "accuracy": 0.34,
    "assists": 1,
    "eliminations": 5,
    "weaponDamage": 612,
    "otherDamage": 40,
    "revives": 0,
    "damageTaken": 260,
    "damageToStructures": 890,
    "materialsGathered": 540,
    "materialsUsed": 310,
    "totalTraveled": 412000
  },
  "players": [
    {
      "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
      "isBot": false,
      "name": "SomePlayer",
      "teamIndex": 12,
      "placement": 8,
      "kills": 2,
      "knocks": 1,
      "deaths": 1,
      "weapons": { "AR": 2, "Shotgun": 1 },
      "positions": [ { "t": 604100, "x": 12345.6, "y": -9876.5, "z": 320.1, "event": "death" } ],
      "killedBy": [ { "t": 604100, "by": "0f1e2d3c4b5a69788796a5b4c3d2e1f0", "weapon": "Sniper", "knocked": false } ],
      "firstSeen": 132400,
      "lastSeen": 604100
    }
  ],
  "elims": [
    {
      "t": 132400,
      "eliminator": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
      "eliminated": "",
      "eliminatorIsBot": false,
      "eliminatedIsBot": true,
      "eliminatorName": null,
      "eliminatedName": null,
      "gunType": 4,
      "gunLabel": "AR",
      "knocked": false,
      "eliminatedPos": { "x": 11200.3, "y": -8100.2, "z": 300.5 },
      "eliminatorPos": { "x": 11150.1, "y": -8050.7, "z": 302.0 }
    },
    {
      "t": 271800,
      "eliminator": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
      "eliminated": "0f1e2d3c4b5a69788796a5b4c3d2e1f0",
      "eliminatorIsBot": false,
      "eliminatedIsBot": false,
      "eliminatorName": null,
      "eliminatedName": null,
      "gunType": 3,
      "gunLabel": "Shotgun",
      "knocked": true,
      "eliminatedPos": { "x": 13000.0, "y": -7000.0, "z": 280.0 },
      "eliminatorPos": { "x": 12980.5, "y": -6990.2, "z": 281.0 }
    },
    {
      "t": 604100,
      "eliminator": "0f1e2d3c4b5a69788796a5b4c3d2e1f0",
      "eliminated": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
      "eliminatorIsBot": false,
      "eliminatedIsBot": false,
      "eliminatorName": null,
      "eliminatedName": null,
      "gunType": 6,
      "gunLabel": "Sniper",
      "knocked": false,
      "eliminatedPos": { "x": 12345.6, "y": -9876.5, "z": 320.1 },
      "eliminatorPos": { "x": 14500.0, "y": -9200.0, "z": 410.3 }
    }
  ],
  "participants": [
    "a1b2c3d4e5f60718293a4b5c6d7e8f90",
    "0f1e2d3c4b5a69788796a5b4c3d2e1f0"
  ],
  "chunkSummary": {
    "header": { "count": 1, "bytes": 2048 },
    "data": { "count": 812, "bytes": 5242880 },
    "checkpoint": { "count": 6, "bytes": 1048576 },
    "event": { "count": 47, "bytes": 6100 }
  },
  "placements": {
    "7": { "winner": false, "epicIds": ["df05386e908940c4a56c748adf7771a9", "c71b783ab45f4c32a9ef1677c5fe0f49"] },
    "8": { "winner": false, "epicIds": ["a1b2c3d4e5f60718293a4b5c6d7e8f90", "2ac1fb3ab5f04b799eb0b835cee0908b"] },
    "9": { "winner": false, "epicIds": ["455c11b4110440b796fbf57a674ee297", "b8e60c36a3044ac38e6970a3760d7762"] }
  }
}

Errors for this endpoint

StatuserrorWhen
400invalid_replayThe file is a replay but is corrupt or unsupported.
413file_too_largeUpload exceeds 35 MB.
415unsupported_fileNot a .replay (fails the magic-byte check).
422parse_timeoutParsing exceeded the time budget.

See the full list, including auth and rate-limit errors, on the Errors page.

How it works & safety

Uploads are never stored or executed

GET /key

Returns your tier and today's usage. Useful for showing quota in a dashboard without spending a parse.

GET/key
json
{
  "tier": "pro",
  "limits": { "perMinute": 60, "perDay": 2000 },
  "usage": { "day": "2026-07-06", "dayCount": 137 }
}

See Rate limits for what these numbers mean.