Skip to main content

REST API

Plain HTTP+JSON endpoints, callable from any language with a Bearer API key. No SDK required.

Base URL: https://sitehealth.octagramlabs.com.

POST /api/external/budget-check​

Runs a pass/fail check of the latest completed scan against the site's configured performance budgets and alert rules. Designed to drop into a CI pipeline (GitHub Actions, GitLab CI, CircleCI) — exit on the pass field.

Auth​

Authorization: Bearer wsh_… (API key only; OAuth tokens are not accepted on this endpoint).

Request schema​

FieldTypeRequiredDescription
siteIdstring (uuid)yesSite to check. Get it from the Settings page or MCP list_sites.
POST /api/external/budget-check HTTP/1.1
Host: sitehealth.octagramlabs.com
Authorization: Bearer wsh_a1b2c3d4e5f6…
Content-Type: application/json

{ "siteId": "11111111-2222-3333-4444-555555555555" }

Response schema​

FieldTypeDescription
passbooleantrue if no violations. Use this as the CI gate.
sitestringSite name
scoresobject{ health, performance, seo, accessibility, bestPractices } — 0–100
violationsarrayEach: { metric, threshold, actual, operator }
scanIdstring (uuid)The scan the check was run against
scannedAtISO timestampWhen the scan completed

Example response (failing)​

{
"pass": false,
"site": "Acme Inc",
"scores": {
"health": 74,
"performance": 68,
"seo": 95,
"accessibility": 91,
"bestPractices": 100
},
"violations": [
{ "metric": "performance", "threshold": 80, "actual": 68, "operator": "min" },
{ "metric": "lcp", "threshold": 2500, "actual": 3420, "operator": "max" }
],
"scanId": "aaaa-bbbb-cccc-dddd-eeee",
"scannedAt": "2026-04-17T10:05:00.000Z"
}

Status codes​

CodeWhen
200Check ran (including pass: false). Also returned if the site has no completed scans yet (pass: false, no scores).
400Body was not valid JSON
401Missing or invalid API key
404Site not found or not owned by the API key's user
422siteId is not a valid UUID

CI integration example (GitHub Actions)​

- name: Performance budget check
env:
SITE_HEALTH_KEY: ${{ secrets.SITE_HEALTH_API_KEY }}
SITE_ID: 11111111-2222-3333-4444-555555555555
run: |
response=$(curl -sS -X POST \
https://sitehealth.octagramlabs.com/api/external/budget-check \
-H "Authorization: Bearer $SITE_HEALTH_KEY" \
-H "Content-Type: application/json" \
-d "{\"siteId\":\"$SITE_ID\"}")
echo "$response" | jq .
echo "$response" | jq -e '.pass == true'

jq -e exits non-zero if pass is not true, failing the step.

tip

Configure your budgets in Settings → Site → Budgets before relying on this endpoint. With no budgets set, violations will always be empty and pass will always be true.

GET /api/sites/{siteId}/export​

Download scan results as CSV or JSON for bulk analysis / reporting.

Auth​

Session cookie only (this endpoint is primarily UI-driven from the "Export" button). It is intentionally not exposed to API keys — API-key-authenticated bulk export is on the Phase 3 roadmap.

Query params​

ParamTypeRequiredDefaultDescription
formatcsv | jsonnocsvOutput format
scanIdstring (uuid)nolatestExport a specific scan. Omit to export the latest completed scan.
GET /api/sites/11111111-…/export?format=csv HTTP/1.1
Host: sitehealth.octagramlabs.com
Cookie: session=…

CSV response​

Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="Acme_Inc-scan-aaaabbbb.csv"

Page URL,Page Path,Strategy,Performance,SEO,Accessibility,Best Practices,LCP (ms),CLS,INP (ms),FCP (ms),TTFB (ms),CrUX LCP,CrUX CLS,CrUX INP,Has CrUX Data,Top Opportunities
https://acme.com/,/,mobile,82,95,91,100,2300,0.08,180,1200,420,2100,0.05,150,yes,"Eliminate render-blocking resources (520ms); Defer offscreen images (310ms)"
…

JSON response​

[
{
"pageUrl": "https://acme.com/",
"pagePath": "/",
"strategy": "mobile",
"scores": { "performance": 82, "seo": 95, "accessibility": 91, "bestPractices": 100 },
"coreWebVitals": { "lcp": 2300, "cls": 0.08, "inp": 180, "fcp": 1200, "ttfb": 420 },
"crux": { "lcp": 2100, "cls": 0.05, "inp": 150, "hasCruxData": true },
"opportunities": [{ "title": "Eliminate render-blocking resources", "savingsMs": 520 }],
"thirdPartyScripts": [{ "domain": "www.googletagmanager.com", "transferSize": 45000, "blockingTime": 220 }],
"a11yIssues": [{ "id": "color-contrast", "title": "…", "score": 0 }]
}
]

Status codes​

CodeWhen
200File returned
401No session cookie
404Site not found, or no completed scans for the site

Planned (Phase 3)​

These endpoints are on the roadmap but not yet shipped. Interface shown is indicative.

POST /api/external/scan (planned)​

Trigger a scan remotely — same as clicking "Scan now" in the UI.

POST /api/external/scan
Authorization: Bearer wsh_…
Content-Type: application/json

{ "siteId": "…", "strategy": "both" }

Response 202: { "scanId": "…", "status": "queued" }.

Rate-limited to 5 scans per user per hour. Deduped against in-flight scans (returns 409 with error: "SCAN_ALREADY_ACTIVE").

warning

Until Phase 3 ships, triggering a scan programmatically requires posting a fake site_publish webhook or kicking a manual scan from the dashboard. Do not rely on POST /api/external/scan existing today.

POST /api/external/webhooks/subscribe (planned)​

Outgoing webhooks (Zapier / Make / n8n) for scan completion, regression detection, and budget violations.

Rate limits​

EndpointLimit
POST /api/external/budget-checkunlimited (read-only)
GET /api/sites/{siteId}/exportunlimited
POST /api/external/scan (planned)5 per user per hour

Next: Webhooks →