Results Analysis API

Every load test result page is built from one JSON document: the whole run's samples, aggregated into percentiles, error types, per-request, per-region and per-user-level rollups. The same document is available to your own tooling through the analysis API, so a dashboard, a report generator or a CI step can read the exact numbers the results page shows.

Endpoint

GET /api/v1/{loadtests|k6tests|jmetertests}/analysis?testrunname=<name>&testrunid=<id>

Send your API key in the loadfocus-auth header. Use loadtests for cloud tests, k6tests for k6 scripts and jmetertests for JMeter plans. Both query parameters are required; testrunid is the run number shown in the results URL (see What is a Test Run ID). Requests are limited to 100 per 15 minutes per API key.

curl -H "loadfocus-auth: $LOADFOCUS_API_KEY" \
"https://loadfocus.com/api/v1/loadtests/analysis?testrunname=checkout-flow&testrunid=12"

A finished run is computed once and cached, so repeated calls are cheap. A run that is still in progress returns its numbers so far.

Responses

StatusBodyMeaning
200the analysis documentThe run exists and has samples.
404{ "error": "no-data" }Unknown run, a run owned by another team, or no samples recorded yet.
503{ "error": "analysis-unavailable" }The computation failed or timed out. Retry; do not treat it as "no data" in a CI gate.
400{ "error": "testrunname-and-testrunid-required" }A parameter is missing.
400{ "error": "invalid-api-key" }The API key does not resolve to a team.
429{ "error": "Too many requests, please try again later." }More than 100 requests in 15 minutes for this API key.

The document

{
"samples": 50312,
"overall": {
"mean": 212.4, "p50": 180, "p90": 410, "p95": 612, "p99": 1480,
"min": 41, "max": 5120, "sd": 233.1, "latency": 198.7, "connect": 9.2,
"errors": 137, "errorPct": 0.3, "rps": 83.9,
"first": 1756540800000, "last": 1756541400000
},
"labels": [ { "label": "GET /api/products", "samples": 25100, "mean": 190.2, "p50": 160, "p90": 380, "p95": 540, "p99": 1200, "errors": 12, "errorPct": 0.1, "rps": 41.8, "min": 41, "max": 3100, "sd": 201.0, "latency": 180.1, "connect": 8.9 } ],
"byThreads": [ { "vus": 50, "samples": 4210, "mean": 150.3, "max": 900, "errors": 0, "rps": 44.1, "errorPct": 0 } ],
"byRegion": [ { "region": "us-east-1", "samples": 25156, "mean": 170.5, "p95": 480, "p99": 1100, "errors": 40, "errorPct": 0.2, "rps": 41.9 } ],
"errorTypes": [ { "label": "POST /api/checkout", "type": "502", "count": 90, "message": "Bad Gateway", "messages": 90, "topMessages": [ { "message": "Bad Gateway", "count": 90 } ] } ],
"perSecond": [ { "t": 1756540800000, "samples": 62, "errors": 0, "errorPct": 0 } ],
"histogram": { "step": 50, "buckets": [ { "from": 0, "to": 50, "count": 812 } ], "over": 31 },
"apdex": { "t": 500, "satisfied": 44100, "tolerating": 5200, "frustrated": 1012, "score": 0.93 },
"settled": true
}
  • samples is the number of recorded requests across all engines and regions. Every other number is computed from those samples, not from per-engine averages.
  • overall is the whole run. Times are milliseconds; latency is time to first byte, connect is connection time. rps is samples divided by the run's active seconds. first and last are epoch milliseconds.
  • labels is one row per request (URL or sampler name) with the same fields plus its own percentiles.
  • byThreads groups samples by the number of virtual users active when they ran. Read it as response time versus load: the level where mean or max jumps is your capacity.
  • byRegion is one row per cloud region, for multi-region runs.
  • errorTypes is one row per request and failure type (type is the HTTP status code or the JMeter exception name), at most the 100 most frequent, with the most frequent error message and up to five topMessages.
  • perSecond is the error rate over time; long runs are downsampled to at most 3,600 points.
  • histogram is the response-time distribution the results page draws, with the count of samples above the last bucket in over.
  • apdex uses a 500 ms target: a sample is satisfied up to 500 ms, tolerating up to 2,000 ms and frustrated above that. score is (satisfied + tolerating / 2) / total.
  • settled is true once the run has finished and its numbers are final. A run in progress returns false; wait for true before you gate on the numbers.

Percentiles come from 1,000 log-spaced buckets, so p99 is exact to within a fraction of a percent of the value; it will not match a spreadsheet computed on the raw JTL to the millisecond.

Gating a pipeline

For a pass/fail decision use the verdict API instead: it applies the thresholds saved on the test and returns a verdict, which is what the GitHub Action reads. Use the analysis API when you need the numbers themselves, for example to post p95 per endpoint into a pull request comment or to keep your own trend history.