--- title: "API Reference" slug: "api-reference" updated: 2026-03-26T15:04:43Z published: 2026-03-26T15:04:43Z canonical: "docs.oscal.io/api-reference" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.oscal.io/llms.txt > Use this file to discover all available pages before exploring further. # API Reference ## 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 base URL is `https://registry.oscal.io/api/v1/`. All API 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=" \ https://registry.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://registry.oscal.io/api/v1/catalogs ``` ``` # List all system security plans curl https://registry.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://registry.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76 ``` ``` # Save to a file curl -o catalog.json \ https://registry.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=" \ -d @my-catalog.json \ https://registry.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=" \ -d @my-catalog-updated.json \ https://registry.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=" \ https://registry.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=" \ https://registry.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://registry.oscal.io/api/v1/catalogs/4429306e-0300-4900-9414-c340fe372f76/versions/2 ``` ``` # Compare two versions using diff diff \ <(curl -s https://registry.oscal.io/api/v1/catalogs/$UUID/versions/1 | jq .) \ <(curl -s https://registry.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=" \ https://registry.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=" \ -d @my-ssp.json \ https://registry.oscal.io/api/upload ``` **Example - Upload a File** ``` curl -X POST \ -H "Cookie: appSession=" \ -F "file=@my-catalog.json" \ https://registry.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://registry.oscal.io/api/v1/$model | python3 -m json.tool done ``` #### Download, Edit, and Re-upload a Document ``` BASE=https://registry.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=" \ -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=" \ -d @my-new-document.json \ https://registry.oscal.io/api/upload ``` #### Use with jq for Scripting ``` # Get all catalog titles curl -s https://registry.oscal.io/api/v1/catalogs \ | jq -r '.[].title' # Get the content-uuid of the first catalog curl -s https://registry.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://registry.oscal.io/api/v1/$model | jq length) echo "$model: $count" done ``` #### Fetch and Open in OSCAL Viewer The registry integrates with [viewer.oscal.io](https://viewer.oscal.io). You can construct viewer links programmatically: ``` https://viewer.oscal.io/{model}/?url={encoded-api-url} ``` Example: ``` BASE=https://registry.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://registry.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://registry.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://registry.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://registry.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://registry.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.