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
| Status | Body | Meaning |
|---|---|---|
200 | the analysis document | The 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 }}
samplesis the number of recorded requests across all engines and regions. Every other number is computed from those samples, not from per-engine averages.overallis the whole run. Times are milliseconds;latencyis time to first byte,connectis connection time.rpsis samples divided by the run's active seconds.firstandlastare epoch milliseconds.labelsis one row per request (URL or sampler name) with the same fields plus its own percentiles.byThreadsgroups samples by the number of virtual users active when they ran. Read it as response time versus load: the level wheremeanormaxjumps is your capacity.byRegionis one row per cloud region, for multi-region runs.errorTypesis one row per request and status code that failed, with the most frequent error message and up to fivetopMessages.perSecondis the error rate over time; long runs are downsampled to at most 3,600 points.histogramis the response-time distribution the results page draws, with the count of samples above the last bucket inover.apdexuses a 500 ms target:scoreis (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.