API Reference

Prev Next

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 Type URL Segment
Catalog catalogs
Profile profiles
Component Definition component-definitions
System Security Plan system-security-plans
Assessment Plan assessment-plans
Assessment Results assessment-results
Plan of Action and Milestones plans-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:

Code Meaning
400 Bad request / invalid JSON
401 Authentication required
403 Forbidden — not the document owner or admin
404 Document not found
409 Conflict — content-uuid mismatch
415 Unsupported media type
422 Unprocessable — 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

Parameter Type Description
model string One 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The 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

Status Condition
404 Document not found
422 Unknown 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The content-uuid of the document

Headers

Header Value
Content-Type application/json
Cookie appSession=<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

Status Meaning
201 Created New document was created
204 No Content Existing document was updated

Error Responses

Status Condition
400 Invalid JSON body or invalid metadata
401 Not authenticated
403 You are not the owner of this document
409 content-uuid in the document doesn't match the URL
415 Content-Type is not application/json
422 Unknown 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The content-uuid of the document

Headers

Header Value
Cookie appSession=<session-cookie>

Response 204 No Content

The document was successfully deleted.

Error Responses

Status Condition
401 Not authenticated
403 Not the document owner or admin
404 Document not found
422 Unknown 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The content-uuid of the document

Headers

Header Value
Cookie appSession=<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

Status Condition
401 Not authenticated
403 Not the document owner or admin
404 Document not found
422 Unknown 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The content-uuid of the document
version integer The version number (1-based)

Response 200 OK

The complete OSCAL JSON document as stored for that version.

Error Responses

Status Condition
400 Invalid version number
404 Version not found
422 Unknown 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

Parameter Type Description
model string Model type URL segment
contentUuid UUID The content-uuid of the document
version integer The version number to delete

Headers

Header Value
Cookie appSession=<session-cookie>

Response 204 No Content

The version was successfully deleted.

Error Responses

Status Condition
400 Invalid version number
401 Not authenticated
403 Not the document owner or admin
404 Document or version not found
409 Cannot delete the only remaining version
422 Unknown 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

Header Value
Content-Type application/json or multipart/form-data
Cookie appSession=<session-cookie>

Request Body

Option A — JSON body:

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

Option B — Multipart form:

Field Type Description
file File An 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

Status Condition
400 Invalid JSON or missing file
401 Not authenticated
403 Document exists and belongs to another user
415 Unsupported content type
422 Not 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

Constraint Value
Maximum document size Limited by server memory (~50 MB)
Accepted formats OSCAL JSON only (XML and YAML are not supported)
Authentication method Auth0 session cookie
Content-Type for writes application/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 Type Title Content UUID Version
catalog NIST SP 800-53 Rev 5.1.1 a1b2c3d4-0001-4000-8000-000000000001 5.1.1+u4
profile NIST 800-53 Rev 5 Moderate Baseline Sample Tailoring a1b2c3d4-0002-4000-8000-000000000002 1.0.0
component-definition Azure Blob Storage Component Definition a1b2c3d4-0003-4000-8000-000000000003 0.3.2
system-security-plan FedRAMP System Security Plan (SSP) a1b2c3d4-0004-4000-8000-000000000004 fedramp-3.0.0rc1-oscal-1.1.2
assessment-plan CISA SCuBA M365 Assessment Plan a1b2c3d4-0005-4000-8000-000000000005 EXPERIMENTAL
assessment-results CISA SCuBA M365 Assessment Results a1b2c3d4-0006-4000-8000-000000000006 1.0
plan-of-action-and-milestones IFA GoodRead Plan of Action and Milestones a1b2c3d4-0007-4000-8000-000000000007 1.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.