Which version is this replay?
Forts replays only play back correctly on the build they were recorded on. Upload a replay to find out which release that is.
API reference
Identify a Forts replay from its own header. Server-to-server; the browser-facing surface is the form above.
| Endpoint | POST https://forts-tools.com/api/version |
|---|---|
| Request body | multipart/form-data |
| Response body | application/json |
| Authentication | None. No key, no origin allow-list. |
| Browser access | No CORS headers are sent. fetch() from a page is blocked by the browser before the body is readable. Call this endpoint server-side. |
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
replay | multipart body | file | required | One Forts .fwr recording. |
Example request
curl -X POST -F "replay=@your-replay.fwr" \
https://forts-tools.com/api/version
Response
Field names, type names and token values are fixed. They are not translated.
Every field has one type or null. null means the file did not record the value: never words standing in for a value, never 0 for a figure that was never written. An empty array means the file records none, not that the value is missing.
schema is forts-version/2. It changes only on a breaking change; a new field never changes it. Any other value is outside this contract.
| Field | Type | Description |
|---|---|---|
ok | boolean | true on success, false on failure. |
schema | string | Contract version. Currently forts-version/2. |
release | object | The release the recording reports. Fields below. |
map_name | string | null | Map name as the recording carries it. |
duration | string | null | Match length, mm:ss. |
duration_sec | number | null | Match length in seconds, two decimals. |
players | object[] | One object per player. Fields below. |
commanders | object[] | One object per commander. Fields below. |
mods | string[] | Add-on names the recording carries. Empty when it carries none. |
winner | string | null | "left" or "right". |
release | ||
build_id | string | Identifier for this release in the build table. |
version | string | Release number, e.g. 1.38.2. |
revision | string | null | Revision, e.g. r22447. |
released | string | null | Release date, YYYY-MM-DD. |
label | string | Display label. English on every locale. |
exact | boolean | false means the era number in the recording covers several releases; the one named above is the newest it could be, not certainly the one it was recorded on. |
is_current | boolean | Whether this is the release the game currently ships. |
players[] | ||
name | string | null | Player name as the recording carries it. Not redacted. |
side | string | null | "left" or "right". Fixed tokens, not display text. |
steam_id | string | null | Steam account id as the recording carries it. |
commanders[] | ||
name | string | null | Commander name as the recording carries it. Not translated. |
side | string | null | "left" or "right". Fixed tokens, not display text. |
Limits
| Limit | Value | Notes |
|---|---|---|
| File size | 4096 B – 300 MB | |
| File parts | 1 | Sent as replay. If more than one is sent, the last is read. |
| Requests per minute, per caller | 6 | Shared with the upload form on this page. Over it: 429. |
| Retention | none | The upload is deleted as soon as identification finishes. No file is produced and nothing expires. |
Errors
Every failure returns {"ok": false, "error": "…"} with one of the status codes below. error is a human-readable message in the language negotiated from Accept-Language. Branch on the status code, not on the message text.
| Status | Meaning |
|---|---|
200 | Success. |
400 | Missing file part, file below the minimum size, unreadable file, or an invalid parameter value. |
405 | Method other than POST. |
413 | Body above the maximum size. |
422 | File read but not usable: unsupported game version, or nothing decodable. |
429 | Rate limit exceeded. Back off; do not retry immediately. |
500 | Internal error. |
503 | Busy, timed out, or the endpoint is disabled. |
Everything else in the file
The table above is what this page and POST /api/version answer with. For everything the recording says about itself — its map assets, its add-ons, per-side detail, whether it reaches the end of the match — POST /api/replay returns the whole decode as JSON.
| Endpoint | POST https://forts-tools.com/api/replay |
|---|---|
| Request body | multipart/form-data |
| Response body | application/json |
| Authentication | None. No key, no origin allow-list. |
| Browser access | No CORS headers are sent. fetch() from a page is blocked by the browser before the body is readable. Call this endpoint server-side. |
Example request
curl -X POST -F "replay=@your-replay.fwr" \
https://forts-tools.com/api/replay
Response
schema is forts-replay/1. It changes only on a breaking change; a new field never changes it. Any other value is outside this contract.
Limits
| Limit | Value | Notes |
|---|---|---|
| File size | 4096 B - 64 MB | Lower than the form's own ceiling above. One branch of the read costs far more on a recording that stops before the match does, and this ceiling is where even that branch finishes inside the job limit. |
The full field-by-field reference is on the Workbench page.