API Reference
REST API for ingesting, querying, and managing Snapline test reports. Base URL: http://localhost:3847/api (default).
Endpoints summary
| Method | Path | Description |
|---|---|---|
GET | /api/health | Health check and report count |
GET | /api/stats | Dashboard aggregate statistics (supports filters) |
GET | /api/facets | Filter dropdown values (projects, tags, frameworks) |
GET | /api/reports | List report summaries (filterable, paginated) |
GET | /api/reports/:id | Get full stored report |
POST | /api/reports | Ingest a TestRunReport |
DELETE | /api/reports/:id | Delete a report |
All JSON responses use Content-Type: application/json. Request bodies for POST must be JSON.
GET /api/health
Health check. Use for load balancers and startup probes.
Response 200
{
"status": "ok",
"reports": 42
}
| Field | Type | Description |
|---|---|---|
status | string | Always "ok" when server is running |
reports | number | Total reports in database |
GET /api/stats
Aggregate statistics for the dashboard.
Response 200
{
"totalRuns": 10,
"totalSuites": 45,
"totalPassed": 40,
"totalFailed": 5,
"passRate": 89,
"frameworks": ["@vaagatech/snapline-engine", "snapline-engine"],
"projects": [{ "value": "snapline-demo", "count": 2 }],
"tags": [{ "value": "node", "count": 1 }, { "value": "python", "count": 1 }],
"recentRuns": [ /* ReportSummary[] — up to 5 */ ]
}
GET /api/facets
Returns distinct projects, tags, and frameworks with counts for populating filter UI.
Query parameters
| Param | Description |
|---|---|
project | Scope facets to a project |
from / to | ISO date range |
Response 200
{
"projects": [{ "value": "snapline-demo", "count": 2 }],
"tags": [{ "value": "node", "count": 1 }],
"frameworks": [{ "value": "snapline-engine", "count": 2 }]
}
GET /api/reports
List stored report summaries, newest first.
Query parameters
| Param | Description |
|---|---|
project | Filter by project name |
tags | Comma-separated tags (OR by default) |
tagMode | all for AND when multiple tags |
framework | Filter by framework string |
status | passed or failed |
from / to | ISO date range on generatedAt |
search | Search label, project, framework, tags |
limit | Page size (default 50, max 200) |
offset | Skip N reports |
Response 200
{
"total": 100,
"limit": 50,
"offset": 0,
"reports": [
{
"id": "a49cab94-52f8-42aa-a393-73ebcc47384e",
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "@vaagatech/snapline-engine",
"label": "CI build #42",
"project": "snapline-demo",
"tags": ["node", "demo"],
"total": 3,
"passed": 2,
"failed": 1,
"durationMs": 1200,
"environment": { "baseUrl": "http://localhost:3000" }
}
]
}
GET /api/reports/:id
Retrieve a full stored report including nested suite and step data.
Response 200
{
"id": "a49cab94-52f8-42aa-a393-73ebcc47384e",
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "@vaagatech/snapline-engine",
"label": "CI build #42",
"total": 1,
"passed": 0,
"failed": 1,
"durationMs": 450,
"environment": { "baseUrl": "http://localhost:3000" },
"report": {
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "@vaagatech/snapline-engine",
"summary": { "total": 1, "passed": 0, "failed": 1, "durationMs": 450 },
"environment": { "baseUrl": "http://localhost:3000" },
"suites": [ /* TestSuiteResult[] */ ]
}
}
Response 404
{ "error": "Report not found" }
POST /api/reports
Ingest a TestRunReport. This is the endpoint used by pushTestReportToHub.
Request body
A TestRunReport object plus optional metadata:
| Field | Description |
|---|---|
label | Display name in the UI |
project | Logical grouping (e.g. snapline-demo, my-app) |
tags | Array or comma-separated string (e.g. node, ci) |
{
"label": "CI build #42",
"project": "snapline-demo",
"tags": ["node", "demo"],
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "@vaagatech/snapline-engine",
"summary": {
"total": 1,
"passed": 1,
"failed": 0,
"durationMs": 500
},
"environment": { "baseUrl": "https://api.example.com" },
"suites": [
{
"name": "My test suite",
"passed": true,
"results": [
{ "step": "api", "passed": true, "message": "api response matched fixture file" }
]
}
]
}
Query parameters (alternative label)
?label=My+run — label can also be passed as a query parameter instead of in the body.
Response 201
{
"id": "a49cab94-52f8-42aa-a393-73ebcc47384e",
"url": "/reports/a49cab94-52f8-42aa-a393-73ebcc47384e"
}
The url path is relative. Full URL: {hubBaseUrl}{url}.
Response 400
{
"error": "Invalid TestRunReport payload",
"hint": "Expected { generatedAt, framework, summary: { total, passed, failed }, suites: [...] }"
}
Validation rules
generatedAt— required string (ISO 8601)framework— required stringsummary.total,summary.passed,summary.failed— required numberssuites— required array; each suite must havename(string),passed(boolean),results(array)
curl example
curl -X POST http://localhost:3847/api/reports \
-H 'Content-Type: application/json' \
-d '{
"label": "Manual upload",
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "manual",
"summary": { "total": 1, "passed": 1, "failed": 0 },
"suites": [{
"name": "Smoke test",
"passed": true,
"results": [{ "step": "ping", "passed": true }]
}]
}'
DELETE /api/reports/:id
Permanently delete a stored report.
Response 204
Empty body on success.
Response 404
{ "error": "Report not found" }
Data types
DiffResult
{
"path": "segment",
"actual": "premium",
"expected": "standard",
"message": "Value mismatch"
}
TestStepResult
| Field | Type | Required | Description |
|---|---|---|---|
step | string | yes | Step or case identifier |
passed | boolean | yes | Pass/fail |
message | string | no | Human-readable message |
diff | DiffResult | null | no | First mismatch on failure |
processed | unknown | no | Processed live data |
data | unknown | no | Raw API response |
source | unknown | no | Cross-system source |
target | unknown | no | Cross-system target |
token | string | null | no | Auth token (usually redacted) |
match | boolean | no | Cross-system match flag |
TestSuiteResult
{
"name": "Suite display name",
"passed": false,
"results": [ /* TestStepResult[] */ ]
}
TestRunReport
{
"generatedAt": "ISO-8601 string",
"framework": "string",
"summary": {
"total": 0,
"passed": 0,
"failed": 0,
"durationMs": 0
},
"environment": { "key": "value" },
"suites": [ /* TestSuiteResult[] */ ]
}
Request size
JSON body limit is 10 MB. Reports are capped at 500 suites and 1000 steps per suite. Large warehouse comparison reports should use Snapline's JSONL stream format locally; Hub is optimized for standard TestRunReport payloads.
CORS
CORS allows localhost:5173 and localhost:3847 by default. Set ALLOWED_ORIGINS to a comma-separated list when the UI is served from other hosts.
Authentication
When HUB_API_KEY is set, mutating endpoints require the X-Hub-Api-Key request header:
curl -X POST http://localhost:3847/api/reports \
-H "Content-Type: application/json" \
-H "X-Hub-Api-Key: your-secret-key" \
-d @report.json
GET endpoints (list, detail, stats, facets, health) remain public. For broader protection, deploy behind a VPN, reverse proxy with auth, or internal network.