REST API
The web UI drives everything through these endpoints. Base URL: https://policypilot.sabiapps.com
Auth. Browser clients use the session cookie plus a CSRF token from
GET /api/csrf in an x-csrf-token header. Machine clients send
x-api-key and are exempt from CSRF. An API key carries its organisation, so
every response is scoped to it. Create keys under Account.
POST /api/assessments
Run a gap analysis. The endpoint you would wire into CI.
curl -sX POST https://policypilot.sabiapps.com/api/assessments \
-H 'x-api-key: pp_...' \
-H 'content-type: application/json' \
-d '{
"policyId": 1,
"frameworkIds": ["soc2", "iso27001"],
"useAi": true,
"save": true,
"label": "CI run for PR #482"
}'
Pass text instead of policyId to assess an unsaved document. Returns
one entry per framework with the full per-control results, so a pipeline can fail the build on
a regression:
{
"document": { "wordCount": 4821, "sentenceCount": 312 },
"assessments": [{
"id": 17,
"frameworkId": "soc2",
"weightedCoverage": 0.78,
"totals": { "covered": 24, "partial": 9, "missing": 7, "contradicted": 1, "excluded": 0 },
"readiness": { "grade": "C", "verdict": "...", "criticalFailures": 1 },
"priorityGaps": [ { "controlId": "CC6.7", "status": "partial", "severity": "critical", ... } ],
"results": [ {
"controlId": "CC6.1",
"status": "covered",
"score": 1,
"confidence": 0.91,
"requiredGroupsSatisfied": 2,
"requiredGroupsTotal": 2,
"evidence": [ { "text": "Access is granted on least privilege...", "start": 1204 } ],
"rationale": "All 2 required elements of this control are addressed. ..."
} ]
}]
}
weightedCoverage weights each control by severity (critical ×4 down to low ×1).
coverage is the unweighted count. Both are reported because they diverge exactly
when it matters.
Reference data
| Endpoint | Returns |
|---|---|
GET /api/frameworks | All 305 controls' metadata, grouped by framework. |
GET /api/frameworks/:id | One framework in full, including every control's signal table. |
GET /api/frameworks/:id/controls/:controlId | One control plus its computed crosswalk to other frameworks. |
GET /api/topics | The shared topic vocabulary and the multi-framework clusters. |
GET /api/reference/risk | Likelihood, impact, treatment and effectiveness scales. |
Everything else
| Endpoint | Purpose |
|---|---|
GET /api/dashboard | Every headline number on one call. |
GET /api/assessments | List past assessments. |
GET /api/assessments/:id/export?format=json|csv|md | Download a report. |
GET /api/assessments/compare/:frameworkId | Delta between the two most recent runs: which controls improved and which regressed. |
POST /api/assessments/:id/remediation | A remediation plan grouped into workstreams. |
GET /api/policies, POST /api/policies | List and create policies. |
POST /api/policies/:id/versions | Save a new version. Identical text is rejected with 409. |
POST /api/policies/:id/status | draft → review → approved → retired. Invalid jumps are rejected. |
POST /api/policies/generate | Generate a draft from framework controls. |
GET /api/controls/:frameworkId, PUT /api/controls/:frameworkId/:controlId | Read and update the control register. |
GET /api/evidence, POST /api/evidence | Evidence tracker, linked to controls across frameworks. |
GET /api/risks, POST /api/risks, PUT /api/risks/:id | Risk register with computed inherent and residual scores. |
POST /api/risks/from-assessment/:id | Seed the register from an assessment's gaps. |
POST /api/obligations/extract | Pull binding statements out of a contract or regulation. |
GET /api/audit, GET /api/audit/verify | Audit log, and hash-chain verification. Returns 409 if the chain is broken. |
Errors
{
"error": {
"code": "unprocessable",
"message": "Some fields need attention.",
"details": [ { "path": "frameworkIds", "message": "Pick at least one framework." } ],
"requestId": "8f1c…"
}
}
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Invalid transition, or a control scoped out without a reason. |
| 401 | unauthorized | No session and no valid API key. |
| 403 | forbidden | Role does not permit it, or a missing CSRF token. |
| 404 | not_found | No such resource in your organisation. |
| 409 | conflict | Duplicate version, or a broken audit chain on /audit/verify. |
| 422 | unprocessable | Validation failed; see details. |
| 429 | rate_limited | Check the RateLimit-* headers. |
A model outage never produces a 5xx. The model review fails soft and you get the deterministic
assessment with engine: "rules".