OSCAL Content Registry — API Reference

The OSCAL Content Registry exposes a RESTful API for listing, retrieving, creating, updating, and deleting OSCAL documents. All request and response bodies use JSON (application/json).


Base URL

The API is served at https://api.oscal.io. All paths in this document are relative to that base URL.

The web-based registry UI is available at https://registry.oscal.io.


Authentication

Public endpoints (listing and retrieving documents) require no authentication.

Write operations (PUT, DELETE, and POST /api/upload) require an Auth0 session cookie. The cookie is set automatically when a user signs in through the web UI at /auth/login.

Obtaining a Token for Programmatic Access

For server-to-server or CLI usage you can obtain an access token from your Auth0 tenant and pass it as a bearer token, or authenticate through a browser flow and reuse the appSession cookie.

Browser cookie approach (quick testing):

  1. Sign in at {BASE_URL}/auth/login in a browser.
  2. Copy the appSession cookie value from your browser's dev tools.
  3. Pass it with your requests:
curl -H "Cookie: appSession=<value>" \
  https://api.oscal.io/api/v1/catalogs

Note: For production integrations, configure a machine-to-machine application in Auth0 and use the Client Credentials flow to obtain an access token.

Error Responses for Unauthenticated Requests

{
  "status-code": 401,
  "message": "Authentication required"
}

Model Types

The registry supports all seven OSCAL model types. Use the URL segment exactly as shown:

Model TypeURL Segment
Catalogcatalogs
Profileprofiles
Component Definitioncomponent-definitions
System Security Plansystem-security-plans
Assessment Planassessment-plans
Assessment Resultsassessment-results
Plan of Action and Milestonesplans-of-action-and-milestones

Error Format

All errors follow a consistent structure:

{
  "status-code": 422,
  "message": "Human-readable description of the error"
}

Common status codes:

CodeMeaning
400Bad request / invalid JSON
401Authentication required
403Forbidden — not the document owner or admin
404Document not found
409Conflict — content-uuid mismatch
415Unsupported media type
422Unprocessable — invalid model type or OSCAL structure

Endpoints

List Documents by Model Type

GET /api/v1/{model}

Returns all documents of the specified model type. Public — no authentication required.

Path Parameters

ParameterTypeDescription
modelstringOne of the model URL segments listed above

Response 200 OK

[
  {
    "content-uuid": "4429306e-0300-4900-9414-c340fe372f76",
    "title": "NIST SP 800-53 Rev 5",
    "oscal-version": "1.1.2",
    "document-version": "5.0.0",
    "last-modified": "2024-02-06T18:30:00.000Z",
    "self": "/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76"
  }
]

Example

# List all catalogs
curl https://api.oscal.io/api/v1/catalogs
# List all system security plans
curl https://api.oscal.io/api/v1/system-security-plan

Get a Document

GET /api/v1/{model}/{contentUuid}

Returns the full OSCAL JSON document. Public — no authentication required.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document

Response 200 OK

The complete OSCAL JSON document as stored.

{
  "catalog": {
    "uuid": "...",
    "metadata": {
      "title": "NIST SP 800-53 Rev 5",
      "version": "5.0.0",
      "oscal-version": "1.1.2",
      "last-modified": "2024-02-06T18:30:00.000Z"
    },
    "groups": [ "..." ]
  }
}

Error Responses

StatusCondition
404Document not found
422Unknown model type

Example

# Retrieve a specific catalog document
curl https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76
# Save to a file
curl -o catalog.json \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76

Create or Update a Document (PUT)

PUT /api/v1/{model}/{contentUuid}

Creates a new document or replaces an existing one (upsert). Requires authentication. Only the document owner can update an existing document.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document

Headers

HeaderValue
Content-Typeapplication/json
CookieappSession=<session-cookie>

Request Body

A complete, valid OSCAL JSON document. The document must:

  1. Contain a recognized OSCAL root key (e.g., "catalog", "profile")
  2. Match the model type in the URL
  3. Contain a content-uuid (inside metadata) that matches the URL parameter, or a uuid at the model root from which the content-uuid can be derived

Response

StatusMeaning
201 CreatedNew document was created
204 No ContentExisting document was updated

Error Responses

StatusCondition
400Invalid JSON body or invalid metadata
401Not authenticated
403You are not the owner of this document
409content-uuid in the document doesn't match the URL
415Content-Type is not application/json
422Unknown model type or document structure invalid

Example — Create a New Catalog

curl -X PUT \
  -H "Content-Type: application/json" \
  -H "Cookie: appSession=<session-cookie>" \
  -d @my-catalog.json \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76

Example — Update an Existing Document

# Modify the local file, then PUT it back
curl -X PUT \
  -H "Content-Type: application/json" \
  -H "Cookie: appSession=<session-cookie>" \
  -d @my-catalog-updated.json \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76

Delete a Document

DELETE /api/v1/{model}/{contentUuid}

Deletes a document. Requires authentication. Only the document owner or an admin can delete a document.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document

Headers

HeaderValue
CookieappSession=<session-cookie>

Response 204 No Content

The document was successfully deleted.

Error Responses

StatusCondition
401Not authenticated
403Not the document owner or admin
404Document not found
422Unknown model type

Example

curl -X DELETE \
  -H "Cookie: appSession=<session-cookie>" \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76

Version History

The registry tracks every update to a document as a numbered version. When you PUT a document, a new version is created automatically — the previous content is preserved and remains accessible through the version endpoints below.

How Versioning Works

  • Each document is identified by its content-uuid.
  • Every PUT to the same content-uuid increments the version number (1, 2, 3, …).
  • GET /api/v1/{model}/{contentUuid} always returns the latest version.
  • Older versions can be retrieved by their version number.

List Versions

GET /api/v1/{model}/{contentUuid}/versions

Returns metadata for all versions of a document, ordered from newest to oldest. Requires authentication — only the document owner or an admin can list versions.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document

Headers

HeaderValue
CookieappSession=<session-cookie>

Response 200 OK

[
  {
    "id": 42,
    "version": 3,
    "title": "NIST SP 800-53 Rev 5 (updated)",
    "documentVersion": "5.0.1",
    "oscalVersion": "1.1.2",
    "lastModified": "2025-06-15T10:00:00.000Z",
    "fileSize": 2048000,
    "createdAt": "2025-06-15T10:00:00.000Z"
  },
  {
    "id": 41,
    "version": 2,
    "title": "NIST SP 800-53 Rev 5",
    "documentVersion": "5.0.0",
    "oscalVersion": "1.1.2",
    "lastModified": "2025-03-08T14:30:00.000Z",
    "fileSize": 2040000,
    "createdAt": "2025-03-08T14:30:00.000Z"
  },
  {
    "id": 40,
    "version": 1,
    "title": "NIST SP 800-53 Rev 5",
    "documentVersion": "5.0.0",
    "oscalVersion": "1.1.2",
    "lastModified": "2025-03-07T09:00:00.000Z",
    "fileSize": 2035000,
    "createdAt": "2025-03-07T09:00:00.000Z"
  }
]

Error Responses

StatusCondition
401Not authenticated
403Not the document owner or admin
404Document not found
422Unknown model type

Example

curl -H "Cookie: appSession=<session-cookie>" \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76/versions

Get a Specific Version

GET /api/v1/{model}/{contentUuid}/versions/{version}

Returns the full OSCAL JSON for a specific version. Public — no authentication required.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document
versionintegerThe version number (1-based)

Response 200 OK

The complete OSCAL JSON document as stored for that version.

Error Responses

StatusCondition
400Invalid version number
404Version not found
422Unknown model type

Example

# Retrieve version 2 of a catalog
curl https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76/versions/2
# Compare two versions using diff
diff \
  <(curl -s https://api.oscal.io/api/v1/catalogs/$UUID/versions/1 | jq .) \
  <(curl -s https://api.oscal.io/api/v1/catalogs/$UUID/versions/2 | jq .)

Delete a Specific Version

DELETE /api/v1/{model}/{contentUuid}/versions/{version}

Deletes a specific version of a document. Requires authentication. Only the document owner or an admin can delete versions.

Note: You cannot delete the only remaining version. To remove the document entirely, use DELETE /api/v1/{model}/{contentUuid} instead.

Path Parameters

ParameterTypeDescription
modelstringModel type URL segment
contentUuidUUIDThe content-uuid of the document
versionintegerThe version number to delete

Headers

HeaderValue
CookieappSession=<session-cookie>

Response 204 No Content

The version was successfully deleted.

Error Responses

StatusCondition
400Invalid version number
401Not authenticated
403Not the document owner or admin
404Document or version not found
409Cannot delete the only remaining version
422Unknown model type

Example

# Delete version 1 of a catalog
curl -X DELETE \
  -H "Cookie: appSession=<session-cookie>" \
  https://api.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76/versions/1

Upload a Document (Auto-detect)

POST /api/upload

Upload an OSCAL document with automatic model-type and content-uuid detection. Accepts either a JSON body or a multipart file upload. Requires authentication.

If a document with the same content-uuid already exists and belongs to the authenticated user, it will be updated. If it belongs to another user, the request will be rejected with 403.

Headers

HeaderValue
Content-Typeapplication/json or multipart/form-data
CookieappSession=<session-cookie>

Request Body

Option A — JSON body:

{
  "catalog": {
    "uuid": "...",
    "metadata": { "..." }
  }
}

Option B — Multipart form:

FieldTypeDescription
fileFileAn OSCAL JSON file

Response 201 Created (new) or 200 OK (updated)

{
  "content-uuid": "4429306e-0300-4900-9414-c340fe372f76",
  "model-type": "catalog",
  "title": "My Catalog",
  "action": "created"
}

Error Responses

StatusCondition
400Invalid JSON or missing file
401Not authenticated
403Document exists and belongs to another user
415Unsupported content type
422Not a valid OSCAL document

Example — Upload JSON Directly

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Cookie: appSession=<session-cookie>" \
  -d @my-ssp.json \
  https://api.oscal.io/api/upload

Example — Upload a File

curl -X POST \
  -H "Cookie: appSession=<session-cookie>" \
  -F "file=@my-catalog.json" \
  https://api.oscal.io/api/upload

Quick-Start Examples

Browse the Registry

# List all model types and their documents
for model in catalogs profiles component-definitions system-security-plans \
  assessment-plans assessment-results plans-of-action-and-milestones; do
  echo "=== $model ==="
  curl -s https://api.oscal.io/api/v1/$model | python3 -m json.tool
done

Download, Edit, and Re-upload a Document

BASE=https://api.oscal.io
DOC_UUID=4429306e-0300-4900-9414-c340fe372f76

# 1. Download
curl -s "$BASE/api/v1/catalogs/$DOC_UUID" -o catalog.json

# 2. Edit the file with your preferred editor
#    (update title, add controls, etc.)

# 3. Re-upload via PUT
curl -X PUT \
  -H "Content-Type: application/json" \
  -H "Cookie: appSession=<session-cookie>" \
  -d @catalog.json \
  "$BASE/api/v1/catalogs/$DOC_UUID"

Upload a New Document (Let the Server Detect Everything)

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Cookie: appSession=<session-cookie>" \
  -d @my-new-document.json \
  https://api.oscal.io/api/upload

Use with jq for Scripting

# Get all catalog titles
curl -s https://api.oscal.io/api/v1/catalogs \
  | jq -r '.[].title'

# Get the content-uuid of the first catalog
curl -s https://api.oscal.io/api/v1/catalogs \
  | jq -r '.[0]."content-uuid"'

# Count documents by model type
for model in catalogs profiles component-definitions; do
  count=$(curl -s https://api.oscal.io/api/v1/$model | jq length)
  echo "$model: $count"
done

Fetch and Open in OSCAL Viewer

The registry integrates with viewer.oscal.io. You can construct viewer links programmatically:

https://viewer.oscal.io/{model}/?url={encoded-api-url}

Example:

BASE=https://api.oscal.io
MODEL=catalog
UUID=4429306e-0300-4900-9414-c340fe372f76

# URL-encode the API endpoint and open in viewer
API_URL="$BASE/api/v1/$MODEL/$UUID"
echo "https://viewer.oscal.io/$MODEL/?url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$API_URL', safe=''))")"

Rate Limits & Constraints

ConstraintValue
Maximum document sizeLimited by server memory (~50 MB)
Accepted formatsOSCAL JSON only (XML and YAML are not supported)
Authentication methodAuth0 session cookie
Content-Type for writesapplication/json or multipart/form-data

Sample Data & Testing

The registry ships with seven sample OSCAL documents — one for every model type — so you can exercise the API immediately after seeding.

Seeding the Database

# Load env vars and run the seed script
npm run db:seed

The seed script is idempotent: running it again skips documents that already exist.

Sample Documents

Model TypeTitleContent UUIDVersion
catalogNIST SP 800-53 Rev 5.1.1a1b2c3d4-0001-4000-8000-0000000000015.1.1+u4
profileNIST 800-53 Rev 5 Moderate Baseline Sample Tailoringa1b2c3d4-0002-4000-8000-0000000000021.0.0
component-definitionAzure Blob Storage Component Definitiona1b2c3d4-0003-4000-8000-0000000000030.3.2
system-security-planFedRAMP System Security Plan (SSP)a1b2c3d4-0004-4000-8000-000000000004fedramp-3.0.0rc1-oscal-1.1.2
assessment-planCISA SCuBA M365 Assessment Plana1b2c3d4-0005-4000-8000-000000000005EXPERIMENTAL
assessment-resultsCISA SCuBA M365 Assessment Resultsa1b2c3d4-0006-4000-8000-0000000000061.0
plan-of-action-and-milestonesIFA GoodRead Plan of Action and Milestonesa1b2c3d4-0007-4000-8000-0000000000071.0

Quick Test: List All Documents

curl -s https://api.oscal.io/api/v1/catalogs | jq '.documents[].title'
# → "NIST SP 800-53 Rev 5.1.1"

Quick Test: Retrieve a Specific Document

# Fetch the sample SSP by its content UUID
curl -s https://api.oscal.io/api/v1/system-security-plans/a1b2c3d4-0004-4000-8000-000000000004 \
  | jq '.["system-security-plan"].metadata.title'
# → "FedRAMP System Security Plan (SSP)"

Quick Test: Retrieve Every Model Type

BASE=https://api.oscal.io

for model in catalogs profiles component-definitions system-security-plans \
             assessment-plans assessment-results plans-of-action-and-milestones; do
  echo "=== $model ==="
  curl -s "$BASE/api/v1/$model" | jq '.documents | length'
done

Quick Test: Open in OSCAL Viewer

# Open the sample catalog in the OSCAL Viewer
BASE=https://api.oscal.io
UUID=a1b2c3d4-0001-4000-8000-000000000001
API_URL="$BASE/api/v1/catalogs/$UUID"
echo "https://viewer.oscal.io/catalog/?url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$API_URL', safe=''))")"

Quick Test: Upload a New Document (authenticated)

# Upload the sample catalog via the multipart endpoint
curl -X POST https://api.oscal.io/api/upload \
  -F "file=@seed-data/catalog-800-53r5.json" \
  --cookie "your-session-cookie"

Tip: For authenticated endpoints, sign in through the browser first and copy the appSession cookie from DevTools → Application → Cookies.