API Reference

REST API for ingesting, querying, and managing Snapline test reports. Base URL: http://localhost:3847/api (default).

Endpoints summary

MethodPathDescription
GET/api/healthHealth check and report count
GET/api/statsDashboard aggregate statistics (supports filters)
GET/api/facetsFilter dropdown values (projects, tags, frameworks)
GET/api/reportsList report summaries (filterable, paginated)
GET/api/reports/:idGet full stored report
POST/api/reportsIngest a TestRunReport
DELETE/api/reports/:idDelete 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
}
FieldTypeDescription
statusstringAlways "ok" when server is running
reportsnumberTotal 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

ParamDescription
projectScope facets to a project
from / toISO 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

ParamDescription
projectFilter by project name
tagsComma-separated tags (OR by default)
tagModeall for AND when multiple tags
frameworkFilter by framework string
statuspassed or failed
from / toISO date range on generatedAt
searchSearch label, project, framework, tags
limitPage size (default 50, max 200)
offsetSkip 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:

FieldDescription
labelDisplay name in the UI
projectLogical grouping (e.g. snapline-demo, my-app)
tagsArray 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

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

FieldTypeRequiredDescription
stepstringyesStep or case identifier
passedbooleanyesPass/fail
messagestringnoHuman-readable message
diffDiffResult | nullnoFirst mismatch on failure
processedunknownnoProcessed live data
dataunknownnoRaw API response
sourceunknownnoCross-system source
targetunknownnoCross-system target
tokenstring | nullnoAuth token (usually redacted)
matchbooleannoCross-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.