Technical reference for developers working on or integrating with the OpenCASE publishing server.
For a non-technical overview, see the OpenCASE README.
npm installnpm run devAPI is available at: http://localhost:8080
GET /health
npm run build
npm startdocker-compose up --buildMount your CASE data at: ./data:/app/data
src/
domain/ # Entities, VOs, domain services
application/ # Use-cases (queries + commands)
infrastructure/ # File persistence, auth, logging, schema validation
interfaces/ # HTTP controllers, Express routes, middleware
main.ts # Bootstrap + DI container
Separation of concerns lets you:
- Swap persistence (file to database)
- Add transport layers (GraphQL, gRPC)
- Extend domain logic without touching controllers
Domain
├── CFDocument
├── CFItem
├── CFAssociation
└── CFPackage
Application
├── CreateFramework
└── GetCFPackage
Infrastructure
├── FileFrameworkStore
└── FileCFPackageRepository
Interfaces
├── Admin controllers
└── Public CASE v1.0 / v1.1 controllers
Loose coupling means you can replace any layer independently.
Framework data is stored as versioned files on disk:
data/
tenants/
demo/
v1p1/
frameworks/
doc-123/
doc-123_v0001.json
doc-123_v0002.json
indexes/
documents.json
document-versions.json
items.json
associations.json
Each framework file contains:
{
"CFDocument": { ... },
"CFItems": [ ... ],
"CFAssociations": [ ... ],
"CFRubrics": [ ... ]
}OpenCASE delegates authentication to Keycloak (external OIDC provider). OpenCASE does not issue tokens itself.
Browser SPAs should use Authorization Code with PKCE via Keycloak's OIDC endpoints:
GET {issuer}/protocol/openid-connect/auth- Authorization endpointPOST {issuer}/protocol/openid-connect/token- Token endpointGET {issuer}/.well-known/openid-configuration- OIDC discovery
Where {issuer} is your Keycloak realm URL (e.g. https://YOUR_DOMAIN/realms/opencase).
For server-to-server use, configure a Keycloak service account with Client Credentials grant type.
See the Backend Integration Guide for detailed authentication setup including PKCE flow, token contract, and tenant scoping.
OpenCASE uses Keycloak client roles to control access:
| Scope | Description | Use Cases |
|---|---|---|
case.read |
Read-only access to CASE entities | Public API access, viewing frameworks |
case.write |
Read and write access to CASE entities | Creating/updating frameworks, items, associations |
case.owner |
Per-tenant administrator | Manage accounts, OAuth clients, and tenant data within a specific tenant |
case.admin |
System-wide administrator | Create tenants, manage OAuth clients across all tenants |
Scope Hierarchy:
case.admin- Highest privilege (system-wide)case.owner- Tenant-specific administrationcase.write- Includescase.readpermissionscase.read- Basic read access
Keycloak-issued access tokens are JWTs containing:
iss- Issuer (must match the Keycloak realm URL)aud/azp- Audience / authorized party (must match the tenant client id)tenantId- Tenant identifier (injected by Keycloak mapper, required for tenant-scoped operations)scope- Space-separated list of granted scopessub- Subject (user ID for authorization_code flow, client ID for client_credentials)
| Variable | Description | Default |
|---|---|---|
| PORT | HTTP port | 8080 |
| CASE_DATA_DIR | Root of data directory | ./data |
| JWT_PUBLIC_KEY | RSA public key for validation | none |
| JWT_ISSUER | JWT issuer | example-issuer |
| JWT_AUDIENCE | JWT audience | example-audience |
Read-only. Follows the official spec.
GET /ims/case/v1p1/CFPackages/{id}
GET /ims/case/v1p1/CFDocuments
- CFDocuments
- CFItems
- CFAssociations
- CFRubrics
- CFPackages
- Supports fields filtering, pagination, sorting, and metadata filtering
- Fully compatible with CASE 1.0 and CASE 1.1 REST bindings
Allows creation and updating of frameworks:
POST/management/tenants/:tenantId/ims/case/v1p1/CFPackagesPUT/management/tenants/:tenantId/ims/case/v1p1/CFDocuments/:idPUT/management/tenants/:tenantId/ims/case/v1p1/CFItems/:idPUT/management/tenants/:tenantId/ims/case/v1p1/CFAssociations/:id
Each update generates a new immutable version on disk.
The Management API provides extended functionality beyond the CASE standard. These endpoints allow you to:
- Update and Delete CASE entities (CFDocuments, CFItems, CFAssociations)
- Manage Tenants (create, list)
- Manage User Accounts (create, update, delete, list, manage memberships)
- Manage OAuth Clients (create, delete, list)
- List Frameworks for a tenant
All management endpoints require authentication and are scoped to the authenticated tenant.
Update CFDocument:
PUT /management/tenants/{tenantId}/CFDocuments/{id}
Authorization: Bearer {access_token}
Content-Type: application/json
{
"CFDocument": { /* Updated CFDocument */ }
}Delete CFDocument:
DELETE /management/tenants/{tenantId}/CFDocuments/{id}
Authorization: Bearer {access_token}Similar endpoints exist for:
PUT /management/tenants/{tenantId}/CFItems/{id}DELETE /management/tenants/{tenantId}/CFItems/{id}PUT /management/tenants/{tenantId}/CFAssociations/{id}DELETE /management/tenants/{tenantId}/CFAssociations/{id}
List Frameworks:
GET /management/tenants/{tenantId}/frameworks?caseVersion=1.1
Authorization: Bearer {access_token}Required Scope: case.admin
List All Tenants:
GET /management/tenants
Authorization: Bearer {access_token}Create a New Tenant:
POST /management/tenants
Authorization: Bearer {access_token}
Content-Type: application/json
{
"tenantId": "new-tenant-id"
}Response:
{
"status": "created",
"tenantId": "new-tenant-id",
"adminAccount": {
"email": "admin@new-tenant-id.local",
"password": "auto-generated-password"
}
}When a tenant is created, an admin account is automatically created with:
- Email:
admin@{tenantId}.local - Auto-generated password (returned in response)
- Role:
admin - Scopes:
case.read,case.write,case.owner
Required Scope: case.owner
Create Account:
POST /management/tenants/{tenantId}/accounts
Authorization: Bearer {access_token}
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure-password",
"role": "user",
"autoGeneratePassword": false
}Roles and Default Scopes:
admin→case.read,case.write,case.owneruser→case.read,case.writeviewer→case.read
List Accounts:
GET /management/tenants/{tenantId}/accounts
Authorization: Bearer {access_token}Update Account:
PUT /management/tenants/{tenantId}/accounts/{accountId}
Authorization: Bearer {access_token}
Content-Type: application/json
{
"email": "newemail@example.com",
"password": "new-password"
}Delete Account:
DELETE /management/tenants/{tenantId}/accounts/{accountId}
Authorization: Bearer {access_token}Add Tenant Membership:
POST /management/tenants/{tenantId}/accounts/{accountId}/memberships
Authorization: Bearer {access_token}
Content-Type: application/json
{
"tenantId": "other-tenant-id",
"role": "user"
}Remove Tenant Membership:
DELETE /management/tenants/{tenantId}/accounts/{accountId}/memberships/{targetTenantId}
Authorization: Bearer {access_token}Required Scope: case.owner or case.admin
Create OAuth Client:
POST /management/tenants/{tenantId}/clients
Authorization: Bearer {access_token}
Content-Type: application/json
{
"clientId": "optional-client-id",
"clientSecret": "optional-secret",
"grantTypes": ["client_credentials", "authorization_code"],
"scopes": ["case.read", "case.write"],
"active": true,
"autoGenerateSecret": false
}List OAuth Clients:
GET /management/tenants/{tenantId}/clients
Authorization: Bearer {access_token}Delete OAuth Client:
DELETE /management/tenants/{tenantId}/clients/{clientId}
Authorization: Bearer {access_token}- Full implementation of all CASE endpoints
- Automated index regeneration tool
- JSON Schema validation for admin payloads
- Optional read/write locking for concurrent edits
- Plugin system for alternate persistence engines (Mongo, Neo4j, S3)
- Certification test suite automation
Issues and PRs welcome — this is a reference implementation, so clarity over cleverness. If adding persistence engines or admin operations, follow the Clean Architecture boundaries.