Validation API
Validate OSCAL 1.2.2 JSON documents over HTTP — stateless, anonymous, powered by the same engine as the browser validator.
The Secani OSCAL Validation API validates OSCAL 1.2.2 JSON documents over HTTP. POST the document itself as the request body — no envelope, no account — and receive a machine-readable report from the same engine that powers the browser validator. It is built for machines in flight: CI jobs, integration scripts, and other tools that produce or transform OSCAL documents.
Privacy contract. Your document is processed transiently in memory on EU servers (Frankfurt) and discarded when the response is sent. Nothing is persisted, logged, or used for anything besides producing the response; telemetry is aggregate only (model type, size bucket, outcome, duration). The browser validator remains fully browser-local — your document never leaves the browser there.
Quickstart
curl -sS -X POST https://secani.com/api/oscal/v1/validate \
-H "content-type: application/json" \
--data-binary @system-security-plan.jsonLarge artifacts (full catalogs can exceed the 4 MB raw body limit) compress extremely well — send them gzipped:
gzip -c catalog.json | curl -sS -X POST https://secani.com/api/oscal/v1/validate \
-H "content-type: application/json" \
-H "content-encoding: gzip" \
--data-binary @-Response
A completed validation always returns HTTP 200 — "valid": false is a successful validation of an invalid document. Branch on the valid field, not the status code.
{
"valid": false,
"model": "system-security-plan",
"oscalVersion": "1.2.2",
"level": "schema",
"issues": [
{
"path": "/system-security-plan/metadata/version",
"message": "must have required property 'version'",
"keyword": "required",
"severity": "error"
}
],
"issueCount": 37,
"truncated": false,
"meta": {
"engine": "@secani/oscal",
"schemaSource": "NIST OSCAL v1.2.2 release JSON schemas",
"patches": ["profile-combine-method"],
"durationMs": 84
}
}| Field | Meaning |
|---|---|
valid | Whether the document passed schema validation. |
model | Detected OSCAL model (one of the eight OSCAL 1.2.2 models). |
oscalVersion | The oscal-version declared in the document's metadata; unknown if absent. |
level | Validation level that ran. Always schema in v1. |
issues[].path | RFC 6901 JSON Pointer into the submitted document. |
issues[].severity | Always error in v1; warning is reserved for future interpretive rules. |
issueCount | True total number of issues, even when issues is truncated. |
truncated | true when more than 200 issues were found and the list was capped. |
meta.patches | Documented deviations from the raw NIST release schemas. |
Errors
Errors use RFC 9457 problem details (application/problem+json) with a stable machine-readable code:
| Status | Code | Meaning |
|---|---|---|
| 400 | ERR_INVALID_JSON | The body is not well-formed JSON. |
| 400 | ERR_UNKNOWN_MODEL | JSON, but no recognizable OSCAL root model. |
| 400 | ERR_INVALID_CONTENT_ENCODING | Content-Encoding: gzip declared but the body is not valid gzip. |
| 400 | ERR_UNSUPPORTED_PARAMETER | A reserved query parameter carries a not-yet-supported value. |
| 405 | ERR_METHOD_NOT_ALLOWED | Only POST (and OPTIONS) are accepted. |
| 413 | ERR_PAYLOAD_TOO_LARGE | Raw body over 4 MB or decompressed body over 24 MB. |
| 415 | ERR_UNSUPPORTED_MEDIA_TYPE | Content type other than application/json, or an unsupported encoding. |
| 429 | ERR_RATE_LIMITED | Rate limit exceeded; see Retry-After. |
| 500 | ERR_INTERNAL | Unexpected failure; no internals are disclosed. |
Limits
- Request body: 4 MB raw, 24 MB decompressed (
Content-Encoding: gzipis supported and recommended for large documents). - One document per request.
- Anonymous rate limits: 10 requests per minute and 300 per day per client, surfaced through
RateLimit-Limit,RateLimit-Remaining, andRateLimit-Resetresponse headers, withRetry-Afteron 429 responses. Need more? Contact hello@secani.com.
Reserved parameters
The following query parameters are part of the contract but rejected until their functionality ships. Sending an unsupported value returns ERR_UNSUPPORTED_PARAMETER with an explanation:
level— onlyschematoday. A deeper constraint- and reference-checking level is planned on top of Secani's extended validation registry.rules— reserved for opting into individual interpretive rules alongside the deeper level.oscal-version— only1.2.2today.
API keys with higher, durable rate limits are planned; the anonymous tier stays.
OpenAPI
The machine-readable contract is served at /api/oscal/v1/openapi.json. CORS is open (*), so the API can be called directly from browser-based tools.
See also: the browser validator for interactive use and the TypeScript toolkit behind both.