Forts Tools

Read your ranked match log

Forts keeps a running record of your ranked matches on your own machine. Upload it and this turns it into a table: every match, the map, who won, what your score did, and how much damage each side dealt.

Maximum file size 32 MB.

Look up a player

Enter a Steam ID or a player name to open that player's ranked matches.

What it shows you

The game writes it as log2.dat for ranked matches or log3.dat for other online matches, in its own folder under the Steam install — steamapps/common/Forts/users/<your account>/ — beside your replays folder.

API reference

Decode a Forts telemetry log to JSON. Server-to-server; the browser-facing surface is the form above.

EndpointPOST https://forts-tools.com/api/telemetry
Request bodymultipart/form-data
Response bodyapplication/json
AuthenticationNone. No key, no origin allow-list.
Browser accessNo CORS headers are sent. fetch() from a page is blocked by the browser before the body is readable. Call this endpoint server-side.

Parameters

NameInTypeRequiredNotes
logfilemultipart bodyfilerequiredThe game's telemetry log — the same file the form above takes. Ranked and network logs are both accepted; kind says which one was read.
samplesquery stringstringoptionalInclude the per-match time series. On: 1, true, yes, on. Off (default): 0, false, no, off. Any other value: 400. The series is one point every few seconds of play; over a full season it is larger than the rest of the response.

Example request

curl -X POST -F "logfile=@your-log.dat" \
     https://forts-tools.com/api/telemetry

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-telemetry/1. It changes only on a breaking change; a new field never changes it. Any other value is outside this contract.

FieldTypeDescription
okbooleantrue on success, false on failure.
schemastringContract version. Currently forts-telemetry/1.
releasesstring[]Game releases the file recorded, sorted and deduplicated. Empty when it recorded none.
summaryobjectWhole-file figures. Fields below.
matchesobject[]One object per resolved match, oldest first. Fields below.
matches_totalintegerMatches resolved in the file. On this endpoint it equals the length of matches.
rejectionsobject[]Lines the reader could not resolve, grouped by message. Fields below.
samples_requestedbooleanWhether ?samples=1 asked for the series.
samplesobjectThe per-match time series. Fields below. Present but empty unless requested.
downloadsobject[]Files generated from the upload. The full decoded text is here, not in the body. Fields below.
expires_in_secintegerSeconds the downloads URLs stay fetchable.
summary
kindstring"ranked" or "network".
lines_totalintegerLines in the decoded file.
lines_understoodintegerLines the reader resolved.
understood_pctintegerlines_understood as a percentage of lines_total, rounded.
readable_pctnumberShare of the file that decoded to text, as a percentage.
samples_totalintegerSample points across all matches.
matches_with_seriesintegerMatches that carry a sample series.
unresolvedintegerMatch blocks the reader could not close.
countersobjectEvent tallies. Keys: launches, forfeits, technical, lobbies, timeouts, uploads. An empty object means nothing was counted.
low_confidencebooleanThe reading is incomplete: readable_pct below threshold, or part of the file did not decode to text.
first_seenstring | nullEarliest date in the file, YYYY-MM-DD.
last_seenstring | nullLatest date in the file, YYYY-MM-DD.
season_firstinteger | nullLowest season number in the file.
season_lastinteger | nullHighest season number in the file.
winsinteger | nullWins in the season-to-date record, as of the most recent match in the file. The counter resets each season, so this is not a career total and does not add up to matches_total.
lossesinteger | nullLosses in the same season-to-date record, on the same terms as wins.
score_firstinteger | nullFirst leaderboard score in the file.
score_lastinteger | nullLast leaderboard score in the file.
score_bestinteger | nullHighest leaderboard score in the file.
matches[]
datestringMatch date, YYYY-MM-DD.
timestringMatch start time, HH:MM:SS.
seasonintegerRanked season number.
samplesintegerSample points recorded for this match. 0 means none.
notesstring[]Flags for this match. Values: won_by_forfeit, lost_by_forfeit, you_forfeited, technical_issue, winner_disputed, pre_game_winner, score_disagrees. [] means none.
map_namestring | nullMap the match was played on.
releasestring | nullGame release the file recorded for this match.
sidestring | nullThe uploader's team as the file writes it: "Team 1" or "Team 2". Not the "left", "right" tokens used by the replay endpoint.
resultstring | null"won" or "lost", from the uploader's side.
score_afterinteger | nullLeaderboard score after the match.
changestring | nullScore change, signed, e.g. "+14".
opponent_scoreinteger | nullOpponent's leaderboard score.
recordstring | nullSeason-to-date win/loss record after this match, e.g. "44W 9L". Resets each season.
damage_dealtstring | nullDamage dealt, two decimals.
damage_takenstring | nullDamage taken, two decimals.
durationstring | nullLength of play, mm:ss. Taken from the samples, so it excludes lobby and loading.
ping_msinteger | nullMatchmaking ping, in milliseconds.
stake_winstring | nullPoints that a win would have gained, signed.
stake_losestring | nullPoints that a loss would have cost, signed.
rejections[]
messagestringThe line as the file wrote it, cleaned. This is the uploader's own text and is not rewritten.
countintegerHow many lines carried it.
samples
columnsstring[]Column names for every row, in order: seconds, damage_dealt, damage_taken.
matchesobject[]One object per match that carries a series. Fields below.
samples.matches[]
datestringMatch date. Pairs with the matches row of the same date and time.
timestringMatch start time.
map_namestring | nullMap the match was played on.
rowsarray[]One array per sample point.
downloads[]
idstringArtifact id, as it appears in url.
namestringFile name. Always English, whatever this page's language is.
bytesintegerFile size in bytes.
urlstringPath to fetch, relative to the site root.

Figures typed string keep the sign and the decimal places the file's own line carried. They are text, not numbers: parse before doing arithmetic.

Each row holds the values named in samples.columns, in that order: seconds as an integer, then two damage figures as numbers rounded to two decimals, or null. A figure the file never wrote is null, never 0.

Limits

LimitValueNotes
File size256 B – 32 MB
File parts1Sent as logfile. If more than one is sent, the last is read.
Matches per response300Over the cap the request is refused with 422 and nothing is published. The form on this page writes the full table as a download and has no cap.
Sample points per response6,000Over the cap the request is refused. A response is never silently shortened.
Requests per minute, per caller6Shared with the upload form on this page. Over it: 429.
Download link lifetime10 min

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.

StatusMeaning
200Success.
400Missing file part, file below the minimum size, unreadable file, or an invalid parameter value.
405Method other than POST.
413Body above the maximum size.
422File read but not usable: nothing decodable, or over a cap listed above.
429Rate limit exceeded. Back off; do not retry immediately.
500Internal error.
503Busy, timed out, or the endpoint is disabled.