Base URL: http://127.0.0.1:9876 (default, configurable via --port)
All endpoints return JSON. Error responses follow this format:
{
"error": "Error message"
}Check engine status and version.
Response:
{
"status": "ok",
"version": "0.3.0",
"workers": 8
}Get global configuration settings. Backed by the config_entries table; each entry carries UI
metadata (label, description, category, default, min/max). Keys include:
{
"workers": 8,
"maxConnections": 10000,
"defaultTimeout": 30000,
"statsInterval": 100,
"contextPoolSize": 64,
"liveTickIntervalMs": 100,
"liveRetentionMs": 60000
}POST /config validates each key against its registered type and min/max range.
Collections are folders that organize requests in a hierarchy.
List all collections.
Response:
[
{
"id": "col_1234567890",
"name": "My API",
"parentId": null,
"variables": {},
"order": 0,
"createdAt": 1234567890
}
]Create or update a collection. If id is provided and exists, performs an update.
Request:
{
"id": "col_1234567890", // Optional, auto-generated if omitted
"name": "My API",
"parentId": null, // Optional, null for root
"order": 0, // Optional
"variables": {} // Optional, collection-scoped variables
}Response: The saved collection object.
Delete a collection and all its requests (cascading delete).
Response:
{
"message": "Collection deleted successfully",
"id": "col_1234567890"
}List requests in a collection.
Query Parameters:
collectionId(required): Collection ID to fetch requests from
Response:
[
{
"id": "req_1234567890",
"collectionId": "col_1234567890",
"name": "Get Users",
"method": "GET",
"url": "{{baseUrl}}/users",
"params": {},
"headers": {},
"body": "",
"bodyType": "none",
"auth": {},
"preRequestScript": "",
"postRequestScript": "",
"updatedAt": 1234567890
}
]Create or update a request. If id is provided and exists, performs an update.
Request:
{
"id": "req_1234567890", // Optional, auto-generated if omitted
"collectionId": "col_1234567890", // Required for new requests
"name": "Get Users", // Required for new requests
"method": "GET", // Required for new requests: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
"url": "{{baseUrl}}/users", // Required for new requests
"params": {}, // Optional, query parameters
"headers": {}, // Optional, request headers
"body": "", // Optional, request body
"bodyType": "none", // Optional: "none", "json", "text", "form-data", "x-www-form-urlencoded"
"auth": {}, // Optional, authentication config
"preRequestScript": "", // Optional, JavaScript pre-request script
"postRequestScript": "" // Optional, JavaScript test script
}Response: The saved request object.
Delete a request.
Response:
{
"message": "Request deleted successfully",
"id": "req_1234567890"
}List all environments.
Response:
[
{
"id": "env_1234567890",
"name": "Production",
"variables": {
"baseUrl": {
"value": "https://api.example.com",
"enabled": true,
"secret": false
}
},
"updatedAt": 1234567890
}
]Create or update an environment.
Request:
{
"id": "env_1234567890", // Optional, auto-generated if omitted
"name": "Production", // Required for new environments
"variables": { // Optional
"baseUrl": {
"value": "https://api.example.com",
"enabled": true,
"secret": false
}
}
}Response: The saved environment object.
Delete an environment.
Response:
{
"message": "Environment deleted successfully",
"id": "env_1234567890"
}Get global variables (singleton).
Response:
{
"id": "globals",
"variables": {
"apiKey": {
"value": "xxx",
"enabled": true,
"secret": false
}
},
"updatedAt": 1234567890
}Set global variables.
Request:
{
"variables": {
"apiKey": {
"value": "xxx",
"enabled": true,
"secret": false
}
}
}Response: The saved globals object.
Execute a single HTTP request (Design Mode). Returns immediate response with test results.
Request:
{
"method": "POST",
"url": "https://api.example.com/users",
"headers": {
"Content-Type": "application/json"
},
"body": {
"type": "json",
"content": "{\"name\":\"John\"}"
},
"requestId": "req_1234567890", // Optional, links to saved request
"environmentId": "env_1234567890", // Optional, uses environment variables
"preRequestScript": "", // Optional
"postRequestScript": "pm.test('Status is 200', () => pm.expect(pm.response.code).to.equal(200));"
}Response:
{
"runId": "run_1234567890",
"statusCode": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json"
},
"body": "{\"id\":1,\"name\":\"John\"}",
"bodySize": 20,
"timing": {
"totalMs": 245.5,
"wireMs": 245.1,
"queueWaitMs": 0.4,
"dnsMs": 5.2,
"connectMs": 12.3,
"tlsMs": 45.1,
"firstByteMs": 180.2,
"downloadMs": 2.7
},
"testResults": [
{
"name": "Status is 200",
"passed": true
}
],
"consoleLogs": []
}Start a load test run (Vayu Mode).
Request:
{
"method": "GET",
"url": "https://api.example.com/users",
"headers": {},
"body": {
"type": "none",
"content": ""
},
"mode": "constant_rps", // "constant_rps", "constant_concurrency", "ramp_up", or "iterations"
"concurrency": 100, // Target in-flight requests (constant_concurrency / ramp_up target / iterations)
"startConcurrency": 1, // Ramp start concurrency (ramp_up mode)
"duration": "60s", // Duration (constant_rps / constant_concurrency / ramp_up)
"rampUpDuration": "10s", // Ramp-up time (ramp_up mode)
"iterations": 0, // Number of iterations (iterations mode)
"targetRps": 1000, // Target requests per second (constant_rps mode)
"maxInFlight": 10000, // Optional; see "maxInFlight" note below — constant_rps only
"requestId": "req_1234567890", // Optional, links to saved request
"environmentId": "env_1234567890", // Optional
"tests": "" // Optional, deferred validation script
}Response:
{
"runId": "run_1234567890",
"status": "running"
}Concurrency model. constant_concurrency, ramp_up, and iterations are
closed-loop: the engine holds in-flight requests at a target (concurrency,
or the ramp curve from startConcurrency to concurrency) — when a request
completes, another is issued. Throughput is a result (concurrency ÷ latency),
not an input. constant_rps is open-loop: it dispatches at targetRps
regardless of how fast responses return.
maxInFlight. A hard cap on concurrent in-flight requests. It applies
only to constant_rps (the open-loop rate mode), where it bounds how many
requests may be outstanding before the engine drops or queues new ones; default
≈ max(targetRps × 10, 1000). For the closed-loop modes the concurrency
target is the in-flight bound, so maxInFlight is ignored there.
Prefer
GET /metrics/live/:runId(below) for live dashboards — it replays a retained in-memory tick topic with no attach race./stats/:runIdis the legacy DB-polling path; it remains useful for historical retrieval via?format=json&limit=&offset=(paginated time-series read), which the app uses to hydrate the history view.
Stream real-time metrics for a load test using Server-Sent Events (SSE).
Response: SSE stream with events:
event: stats
data: {"timestamp":1234567890,"totalRequests":1500,"totalErrors":5,"totalSuccess":1495,"errorRate":0.33,"avgLatencyMs":45.2,"currentRps":150.5,"activeConnections":100,"elapsedSeconds":10.5}
event: complete
data: {"totalRequests":6000,"totalErrors":30,"totalSuccess":5970,"errorRate":0.5,"avgLatencyMs":42.1,"finalRps":100.0,"duration":60.0}
Metrics included:
totalRequests: Total requests completedtotalErrors: Total errors encounteredtotalSuccess: Total successful requestserrorRate: Error rate as percentageavgLatencyMs: Average latency in millisecondscurrentRps: Current requests per secondactiveConnections: Active concurrent connectionselapsedSeconds: Elapsed time since test start
Stream live metrics for a run via Server-Sent Events, replayed from a retained
in-memory tick topic. The engine produces one wire-ready metrics tick per
liveTickIntervalMs (default 100ms) into a per-run buffer; this endpoint
replays that buffer from offset 0 and then tails new ticks until the run
finishes, ending with a complete event. Because the topic is retained for
liveRetentionMs (default 60000ms) after completion, a client that connects
late — even after a sub-second run has already finished — still receives the
full series. There is no attach race.
Events:
event: metrics
id: 0
data: {"runId":"...","timestamp":1234567890,"elapsedSeconds":10.5,
"totalRequests":1500,"totalSuccess":1495,"totalErrors":5,"errorRate":0.33,
"currentRps":150.5,"sendRate":150.0,"throughput":149.5,
"activeConnections":100,"backpressure":0,"droppedRequests":0,
"avgLatencyMs":45.2,"avgQueueWaitMs":0.4,
"latencyP50Ms":38.5,"latencyP95Ms":95.1,"latencyP99Ms":156.7,
"bytesSent":48000,"bytesReceived":1920000,
"requestsSent":1500,"requestsExpected":0,
"status2xx":1495,"status3xx":0,"status4xx":3,"status5xx":2,
"statusCodes":{"200":1495,"404":3,"500":2}}
event: complete
data: {"event":"complete","runId":"run_1234567890"}
Field reference (all keys emitted by MetricsCollector::get_current_stats()):
| Field | Meaning |
|---|---|
totalRequests / totalSuccess / totalErrors |
Completed counts |
errorRate |
Error percentage |
currentRps |
Instantaneous RPS (delta over the tick window) |
sendRate |
Rate requests are dispatched (open model) |
throughput |
Rate responses are received |
activeConnections |
Current in-flight requests |
backpressure |
Queue depth (requestsSent − totalRequests) |
droppedRequests |
Requests discarded at the maxInFlight cap (never sent) |
avgLatencyMs |
Mean perceived latency |
avgQueueWaitMs |
Mean time queued inside the generator before the wire |
latencyP50Ms / latencyP95Ms / latencyP99Ms |
Live percentiles (cumulative HdrHistogram) |
bytesSent / bytesReceived |
Cumulative wire bytes |
requestsSent / requestsExpected |
Progress for bounded modes (drives ETA) |
status2xx–status5xx |
Per-class counts |
statusCodes |
Full per-code distribution map |
Each metrics event carries an id: equal to its zero-based offset. The
browser's built-in EventSource retry automatically replays this id as
Last-Event-ID on its own intra-connection retries (no application code
needed), and the stream resumes from Last-Event-ID + 1.
Application-level reconnect: clients that close the EventSource themselves
(e.g. after observing readyState === CLOSED) should NOT open a new connection
and rely on Last-Event-ID — EventSource does not expose a header-setting
API, so a fresh connect would request from=0 and replay the entire retained
topic, duplicating ticks already shown. The canonical recovery is to converge
on the stored report via GET /run/:runId/report (the same path used at normal
run end). This is the pattern the bundled app uses.
Responses:
200— SSE stream (active run, or finished run still within the retention window).404— run not found or evicted pastliveRetentionMs; the body hintsUse /run/:runId/report for the stored report. Clients should fall back to the stored report in this case.
Tuning: liveTickIntervalMs (live tick cadence, 10–1000ms) and
liveRetentionMs (post-completion retention, 0–600000ms; 0 disables retention)
are configurable via POST /config.
List all test runs (both design mode and load tests).
Response:
[
{
"id": "run_1234567890",
"requestId": "req_1234567890",
"environmentId": "env_1234567890",
"type": "design",
"status": "completed",
"configSnapshot": "{}",
"startTime": 1234567890,
"endTime": 1234567891
}
]Get details for a specific run.
Response: Run object with full details.
Stop a running load test.
Response:
{
"message": "Run stopped",
"runId": "run_1234567890"
}Get the final report for a completed run. Reconstructed from the runs, metrics, and results
tables (there is no stored summary blob). The response is a nested object; conditional
sections appear only when relevant (e.g. rateControl only for constant_rps, testValidation
only when a test script ran).
Response:
{
"metadata": {
"runId": "run_1234567890",
"runType": "load",
"status": "completed",
"startTime": 1234567890,
"endTime": 1234567950,
"requestUrl": "https://api.example.com/users",
"requestMethod": "GET",
"configuration": { "...": "config snapshot" }
},
"summary": {
"totalRequests": 6000,
"successfulRequests": 5970,
"failedRequests": 30,
"errorRate": 0.5,
"totalDurationSeconds": 60.0,
"avgRps": 100.0,
"testDuration": 60.0,
"sendRate": 100.0,
"throughput": 99.5,
"setupOverhead": 0.12,
"peakConcurrency": 100,
"droppedRequests": 0,
"avgQueueWaitMs": 0.4,
"bytesSent": 192000,
"bytesReceived": 7680000,
"throughputBytesPerSec": 128000
},
"latency": {
"min": 12.3, "max": 1250.5, "avg": 42.1, "median": 38.5,
"p50": 38.5, "p75": 45.2, "p90": 78.3, "p95": 95.1, "p99": 156.7, "p999": 450.2
},
"statusCodes": { "200": 5970, "500": 30 },
"rateControl": { "targetRps": 100, "actualRps": 99.5, "achievement": 99.5 },
"errors": {
"total": 30,
"withDetails": 30,
"types": { "timeout": 20, "connection_failed": 10 },
"byStatusCode": { "500": 30 }
},
"timingBreakdown": {
"avgDnsMs": 5.2, "avgConnectMs": 12.3, "avgTlsMs": 45.1,
"avgFirstByteMs": 180.2, "avgDownloadMs": 2.7
},
"slowRequests": { "count": 12, "thresholdMs": 1000, "percentage": 0.2 },
"testValidation": { "samplesTested": 500, "testsPassed": 498, "testsFailed": 2, "successRate": 99.6 },
"results": [ { "...": "sampled request/response outcomes" } ]
}latency.* and the enriched summary fields (peakConcurrency, droppedRequests,
avgQueueWaitMs, bytesSent/Received, throughputBytesPerSec) come from the persisted
per-tick metrics rows. latency_ms in results (and therefore these percentiles) is
perceived latency.
Delete a run and all associated metrics/results.
Response:
{
"message": "Run deleted successfully",
"runId": "run_1234567890"
}Get script engine API completions for UI autocomplete.
Response:
{
"version": "1.0.0",
"engine": "quickjs",
"completions": [
{
"label": "pm.test",
"kind": 1,
"insertText": "pm.test(\"${1:test name}\", function() {\n\t${2:// assertions}\n});",
"detail": "pm.test(name: string, fn: () => void)",
"documentation": "Define a test with assertions..."
}
]
}| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (invalid JSON, missing required fields) |
| 404 | Resource not found |
| 500 | Internal server error |
- All timestamps are Unix milliseconds (since epoch)
- Variable substitution uses
{{variableName}}syntax - Environment variables are resolved in order: environment → collection → global
- Load test metrics are collected every 100ms
- SSE connections timeout after 30 seconds of inactivity