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.

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.

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", "rc": "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 }
}
  • 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 status code that failed, 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: score is (satisfied + tolerating / 2) / total.

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.