ZagTrader

FIX Log Analysis API

POST a FIX log from your engine, a script or a CI job and get the same statistics as the FIX log analyzer: message counts and rates, orders and outcomes, fill and reject rates, traded quantity and value, latency percentiles, reject reasons and session health. Free, no key, no sign-up. Or skip the server entirely and open a log from your application straight in the browser.

Privacy: an API request is processed in memory, not stored, not logged and not cached. The browser handoff below never reaches our server at all.

Endpoint

POST https://fixparser.zagtrader.com/api/v1/analyze
Query parameterMeaning
formatjson (default), html (a self-contained report file that works offline) or text (the plain-text summary)
clockClock for latency: log (default, the log timestamp), 52 (SendingTime) or 60 (TransactTime)
tzIANA time zone for the times shown, for example Europe/London; default: as written in the log
anonymise1 replaces CompIDs and free text with placeholders (COMP1, TEXT1), as the parser's Anonymise switch does. The statistics never contain accounts, order IDs or party IDs.

GET on the same address returns a short usage document as JSON.

Examples

Raw text
curl --data-binary @fix.log -H "Content-Type: text/plain" "https://fixparser.zagtrader.com/api/v1/analyze"
Gzip (for logs over a few MB)
gzip -c fix.log | curl --data-binary @- -H "Content-Encoding: gzip" "https://fixparser.zagtrader.com/api/v1/analyze"
Multipart file upload (.log, .gz or .zip)
curl -F "file=@fix-logs.zip" "https://fixparser.zagtrader.com/api/v1/analyze"
HTML report to a file
curl --data-binary @fix.log -H "Content-Type: text/plain" "https://fixparser.zagtrader.com/api/v1/analyze?format=html" -o fix-report.html
Anonymised plain-text summary, times in London
curl --data-binary @fix.log -H "Content-Type: text/plain" "https://fixparser.zagtrader.com/api/v1/analyze?anonymise=1&format=text&tz=Europe/London"
PowerShell
$r = Invoke-RestMethod -Method Post -Uri "https://fixparser.zagtrader.com/api/v1/analyze" -InFile .\fix.log -ContentType "text/plain"
$r.stats.orders.fillRate
$r.report_url
Python (requests)
import requests

with open("fix.log", "rb") as f:
    r = requests.post("https://fixparser.zagtrader.com/api/v1/analyze", data=f, headers={"Content-Type": "text/plain"}, params={"clock": "log"})
r.raise_for_status()
stats = r.json()["stats"]
print(stats["orders"]["fillRate"], r.json()["report_url"])

Response

JSON with the input summary, the full statistics and a report_url that opens the charts in the browser. The report link carries the statistics only (totals and top lists, no messages) in the part after #, so opening it sends nothing to a server.

{
  "api": "zagtrader-fix-analytics",
  "version": "1",
  "generated_at": "2026-10-20T12:00:00.000Z",
  "input": {
    "bytes": 2453190, "compressed": false, "messages": 11022, "sessions": 3,
    "fix_versions": ["FIX.4.4", "FIX.4.2", "FIXT.1.1"],
    "first_time": "2026-10-19 09:25:00.000", "last_time": "2026-10-19 16:10:10.021",
    "time_zone": "as written in the log", "clock": "log", "anonymised": false
  },
  "stats": {
    "overview": { "messages": 11022, "sessions": 3, "peakPerSecond": { "count": 62, "ms": 1792402201000 }, "...": "..." },
    "orders": { "orders": 2041, "fills": 2843, "fillRate": 0.754, "rejectRate": 0.0088, "orderToTradeRatio": 0.91, "outcomes": ["..."] },
    "trading": { "symbols": ["..."], "currencies": ["..."], "sides": ["..."], "lastMarkets": ["..."] },
    "rejects": { "total": 24, "session": ["..."], "order": ["..."], "cancel": ["..."], "texts": ["..."] },
    "sessions": [{ "label": "ZAGOMS ↔ BROKERX", "gaps": 1, "resendRequests": 1, "heartBtInt": 30, "...": "..." }],
    "latency": { "clock": "log", "metrics": [{ "metric": "ack", "count": 2037, "p50": 1.77, "p90": 5.27, "p99": 10.2, "max": 2390 }] },
    "timeline": { "bucketMs": 300000, "buckets": ["..."] },
    "skipped": []
  },
  "report_url": "https://fixparser.zagtrader.com/report#r=...",
  "notes": ["Times are shown as written in the log; ..."]
}

Limits and errors

Errors are JSON: { "error": { "code": "no_fix_messages", "message": "..." } }

StatusCodeWhen
400empty_bodyThe body or the file is empty.
400no_fix_messagesNo message starting with 8=FIX was found.
400missing_fileA multipart form without a field named file.
400invalid_parameterformat, clock or tz has a value the API does not accept.
413body_too_largeThe body is over 4.5 MB. Gzip the log first.
413decompressed_too_largeThe log is over 200 MB once decompressed.
415unsupported_media_typeSend text, gzip, zip or multipart/form-data.
422decode_failedThe gzip or zip data is damaged, or the file is not a text log.
429rate_limitedMore than 60 requests a minute from one address.
500internal_errorSomething failed on our side. The message never includes log content.

Open a log from your application

Your engine or tool can show a log in the full parser and analyzer without any upload: it opens the parser in the browser and hands the log over with window.postMessage. The log goes from your application to the browser on the same computer and is never uploaded to ZagTrader or anyone else. The parser page shows “Log received from … Nothing was uploaded.”

From a web page: a button

Open /?handoff=1 on a click (so pop-up blockers allow it), wait for zagtrader-fix-ready, then post the log. A complete page to copy: example-button.html.

JavaScript
const PARSER = "https://fixparser.zagtrader.com";
button.onclick = () => {
  const win = window.open(PARSER + "/?handoff=1", "zagtrader-fix-parser"); // on the click: no pop-up block
  addEventListener("message", function onMessage(e) {
    if (e.source !== win || e.origin !== PARSER) return;
    if (e.data.type === "zagtrader-fix-ready") {
      win.postMessage({ type: "zagtrader-fix-log", version: 1, name: "today.log", text: logText }, PARSER);
      removeEventListener("message", onMessage);
    }
  });
};

From a server or desktop engine: a local viewer file

Copy local-viewer-template.html, put the log inside <script type="text/plain" id="fixlog"> (write every </script in the log as <\/script), save it as a temporary .html file and open it with the default browser: start "" fix-log.html on Windows, xdg-open on Linux, open on macOS. The file shows the parser full-window (/?handoff=1&embed=1 in an iframe) and passes the log in. For large logs, gzip the log, base64-encode it and add data-encoding="base64-gzip"to the script block. The file also has an “Open in a new tab instead” link.

C# / .NET
using System.Diagnostics;
using System.IO;
using System.Net.Http;

// Fill the local viewer template with the log and open it in the default browser.
static async Task OpenInFixParser(string logText, string name = "engine.log")
{
    var template = await new HttpClient().GetStringAsync(
        "https://fixparser.zagtrader.com/handoff/local-viewer-template.html"); // or ship a copy with your app
    var safe = logText.Replace("</script", "<\\/script", StringComparison.OrdinalIgnoreCase);
    var html = template
        .Replace("data-name=\"engine.log\"", $"data-name=\"{System.Net.WebUtility.HtmlEncode(name)}\"")
        .Replace("PUT THE FIX LOG HERE", safe);
    var path = Path.Combine(Path.GetTempPath(), $"fix-log-{Guid.NewGuid():N}.html");
    await File.WriteAllTextAsync(path, html);
    Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
}
Python
import html, pathlib, tempfile, urllib.request, uuid, webbrowser

def open_in_fix_parser(log_text: str, name: str = "engine.log") -> None:
    """Fill the local viewer template with the log and open it in the default browser."""
    template = urllib.request.urlopen(
        "https://fixparser.zagtrader.com/handoff/local-viewer-template.html").read().decode("utf-8")
    safe = log_text.replace("</script", "<\\/script")
    page = (template
            .replace('data-name="engine.log"', f'data-name="{html.escape(name)}"')
            .replace("PUT THE FIX LOG HERE", safe))
    path = pathlib.Path(tempfile.gettempdir()) / f"fix-log-{uuid.uuid4().hex}.html"
    path.write_text(page, encoding="utf-8")
    webbrowser.open(path.as_uri())

Message format

DirectionMessageNotes
Parser to application{ type: "zagtrader-fix-ready", version: 1 }Sent when the parser page has loaded, and again every second until a log arrives.
Application to parser{ type: "zagtrader-fix-log", version: 1, name?: string, text?: string, data?: ArrayBuffer | Uint8Array | Blob, view?: "analytics" | "timeline" }Exactly one of text or data. data may be plain text, gzip or zip bytes. view defaults to analytics. Any other key, a different version or a second log is ignored.
Parser to application{ type: "zagtrader-fix-loaded", version: 1, messages: number }The log is open; messages is the number of FIX messages found.
Parser to application{ type: "zagtrader-fix-error", version: 1, message: string }The log could not be read, or it has no FIX messages.

Running FIX at scale? ZagTrader monitors sessions and order flow across all your FIX connections from one OMS/EMS.