API Endpoints Reference¶
Complete reference for the SCP REST API.
Base URL: http://localhost:8000 (development)
Interactive Documentation: Visit /docs (Swagger UI) or /redoc (ReDoc)
Authentication¶
Agent Authentication (v0.3)¶
Agents authenticate using API keys in the X-API-Key header:
X-API-Key: sk_your_api_key_here
API keys are generated when registering agents (see Agent Registry API).
Admin Authentication¶
Admin operations (bundle registry, agent registry, audit admin reads) require an X-Admin-Key header:
X-Admin-Key: admin_your_key_here
The admin key is set via ADMIN_API_KEY in the deployment environment. Requests without a valid key return 401. Requests to a deployment where the key is not configured return 503.
See Security Overview for key generation and storage guidance.
Context API (v0.3)¶
The primary API for agents to request governed context.
Request Context¶
POST /api/context
Content-Type: application/json
X-API-Key: sk_your_api_key_here
{
"task_type": "prior_auth_check",
"params": {
"drug": "Keytruda",
"diagnosis": "lung cancer"
}
}
Response: 200 OK
{
"success": true,
"request_id": "ctx_abc123def456",
"agent_id": "agent:prior-auth-prod",
"role_id": "role:prior-auth-agent",
"task_type": "prior_auth_check",
"scd_ids": [
"scd:standards:hipaa-privacy-rule",
"scd:project:clinical-guidelines-v1.2",
"scd:project:formulary-2024"
],
"scd_content": {
"scd:standards:hipaa-privacy-rule": { ... },
"scd:project:clinical-guidelines-v1.2": { ... }
},
"timestamp": 1706288400,
"processing_time_ms": 34
}
Error Responses:
401 Unauthorized - Invalid or missing API key
{
"success": false,
"error_code": "AUTH_FAILED",
"error_message": "Invalid or expired API key"
}
403 Forbidden - Task type not allowed for this agent
{
"success": false,
"error_code": "INTENT_NOT_ALLOWED",
"error_message": "Task type 'claims_adjudication' not in agent's allowed intents"
}
Context Health Check¶
GET /api/context/health
Response: 200 OK
{
"status": "healthy",
"service": "control-plane-api",
"version": "1.0.0"
}
Agent Registry API¶
Register Agent¶
POST /api/agents
Content-Type: application/json
{
"agent_id": "agent:prior-auth-prod",
"name": "Prior Auth Production Agent",
"role_id": "role:prior-auth-agent",
"allowed_intents": [
"prior_auth_check",
"formulary_lookup",
"clinical_guidelines_check"
],
"status": "active"
}
Response: 201 Created
{
"agent_id": "agent:prior-auth-prod",
"name": "Prior Auth Production Agent",
"role_id": "role:prior-auth-agent",
"allowed_intents": ["prior_auth_check", "formulary_lookup", "clinical_guidelines_check"],
"status": "active",
"created_at": "2026-01-26T10:00:00Z"
}
List Agents¶
GET /api/agents?role_id=role:prior-auth-agent&status=active
Query Parameters:
- role_id - Filter by role
- status - Filter by status (active, inactive, suspended)
- limit - Max results (default: 100)
- offset - Pagination offset
Response: 200 OK
{
"agents": [
{
"agent_id": "agent:prior-auth-prod",
"name": "Prior Auth Production Agent",
"role_id": "role:prior-auth-agent",
"status": "active",
"created_at": "2026-01-26T10:00:00Z"
}
],
"total": 1
}
Get Agent¶
GET /api/agents/{agent_id}
Response: 200 OK
{
"agent_id": "agent:prior-auth-prod",
"name": "Prior Auth Production Agent",
"role_id": "role:prior-auth-agent",
"allowed_intents": ["prior_auth_check", "formulary_lookup"],
"status": "active",
"created_at": "2026-01-26T10:00:00Z",
"api_keys": [
{
"key_id": "key_prod_default",
"created_at": "2026-01-26T10:00:00Z",
"last_used_at": "2026-01-26T15:30:00Z"
}
]
}
Update Agent¶
PUT /api/agents/{agent_id}
Content-Type: application/json
{
"name": "Updated Agent Name",
"allowed_intents": ["prior_auth_check", "formulary_lookup", "new_intent"],
"status": "active"
}
Response: 200 OK
Generate API Key¶
POST /api/agents/{agent_id}/api-keys
Content-Type: application/json
{
"key_id": "key_prod_v2"
}
Response: 201 Created
{
"key_id": "key_prod_v2",
"api_key": "sk_abc123def456...",
"agent_id": "agent:prior-auth-prod",
"created_at": "2026-01-26T10:00:00Z"
}
Important: The
api_keyis only returned once. Store it securely.
Revoke API Key¶
DELETE /api/agents/{agent_id}/api-keys/{key_id}
Response: 204 No Content
Add Policy Override¶
Add SCD overrides or restrictions for a specific agent.
POST /api/agents/{agent_id}/policy
Content-Type: application/json
{
"overrides": ["scd:project:special-guidelines"],
"restrictions": ["scd:project:phi-handling"]
}
Response: 200 OK
MCP Server Registry API¶
Governed the same way agents are (see Agent Registry API above) — MCP servers/connectors are
proactively registered before they touch regulated data, with an accountable human owner (HITL).
Deliberately a separate table from agents — an MCP server is a tool/connector, not an agent.
Requires the X-Admin-Key header (all routes in this group carry the admin-key dependency).
Register MCP Server¶
POST /api/mcp-servers
Content-Type: application/json
X-Admin-Key: admin_your_key_here
{
"name": "Propel",
"description": "Propel MCP connector — supplier/vendor qualification lookups",
"owner_name": "Caroline",
"owner_verified": false,
"created_by": "tim@ohana-consulting.com"
}
Response: 200 OK
{
"server_id": "mcp_a1b2c3d4e5f6",
"name": "Propel",
"description": "Propel MCP connector — supplier/vendor qualification lookups",
"status": "active",
"owner_name": "Caroline",
"owner_verified": false,
"created_by": "tim@ohana-consulting.com",
"created_at": "2026-07-25T10:00:00Z",
"updated_at": "2026-07-25T10:00:00Z"
}
owner_verified is false until confirmed against a real identity provider (SSO) — same
convention as agents.owner_verified. See Data Dictionary.
List MCP Servers¶
GET /api/mcp-servers?status=active&limit=50&offset=0
X-Admin-Key: admin_your_key_here
Query Parameters: status (filter), limit (default 50, max 100), offset.
Response: 200 OK — { "servers": [ ... ], "total": 1 }
Get / Update / Delete MCP Server¶
GET /api/mcp-servers/{server_id}
PUT /api/mcp-servers/{server_id}
DELETE /api/mcp-servers/{server_id}
All require X-Admin-Key. PUT accepts a partial update (any subset of name, description,
status, owner_name, owner_verified).
Bundle Registry API¶
Create/Publish Bundle¶
POST /api/bundles
Content-Type: application/json
{
"id": "bundle:my-bundle",
"type": "concern",
"version": "1.0.0",
"title": "My Bundle",
"description": "Bundle description",
"scds": ["scd:project:my-scd"],
"imports": [],
"provenance": {
"created_by": "user@example.com",
"created_at": "2026-01-26T10:00:00Z",
"rationale": "Initial bundle creation"
}
}
Response: 201 Created
{
"bundle_id": "bundle:my-bundle",
"version": "1.0.0",
"status": "published"
}
List Bundles¶
GET /api/bundles?type=concern&limit=100
Query Parameters:
- type - Filter by bundle type (meta, standards, domain, concern, project)
- limit - Max results (default: 100)
- offset - Pagination offset
Response: 200 OK
{
"bundles": [
{
"id": "bundle:my-bundle",
"version": "1.0.0",
"type": "concern",
"title": "My Bundle",
"created_at": "2026-01-26T10:00:00Z"
}
],
"total": 1
}
Get Bundle Version¶
GET /api/bundles/{bundle_id}/versions/{version}
Response: 200 OK
{
"id": "bundle:acme-security",
"type": "concern",
"version": "1.0.0",
"title": "Acme Security Controls",
"description": "Security standards for Acme Health Corp",
"scds": ["scd:project:security-controls"],
"imports": [],
"manifest": { ... }
}
Deactivate Bundle¶
DELETE /api/bundles/{bundle_id}/versions/{version}
Response: 204 No Content
Tag Management API¶
Tags are mutable pointers to immutable bundles.
Create Tag¶
POST /api/tags
Content-Type: application/json
{
"tag": "prior-auth:latest",
"bundle_id": "bundle:prior-auth",
"bundle_version": "1.0.0"
}
Response: 201 Created
{
"tag": "prior-auth:latest",
"bundle_id": "bundle:prior-auth",
"bundle_version": "1.0.0",
"created_at": "2026-01-26T10:00:00Z"
}
Update Tag¶
Move a tag to point to a different bundle version (zero-downtime update).
PUT /api/tags/{tag}
Content-Type: application/json
{
"bundle_id": "bundle:prior-auth",
"bundle_version": "1.1.0"
}
Response: 200 OK
{
"tag": "prior-auth:latest",
"bundle_id": "bundle:prior-auth",
"bundle_version": "1.1.0",
"updated_at": "2026-01-26T12:00:00Z"
}
Resolve Tag¶
GET /api/tags/{tag}
Response: 200 OK
{
"tag": "prior-auth:latest",
"bundle_id": "bundle:prior-auth",
"bundle_version": "1.1.0",
"resolved_bundle_ids": [
"bundle:prior-auth:1.1.0",
"bundle:acme-health-corp:1.0.0",
"bundle:hipaa:1.0.0"
],
"context_yaml": "..."
}
Deployment API¶
Deploy Bundle¶
POST /api/deploy
Content-Type: application/json
{
"project_bundles": [
{
"id": "bundle:prior-auth",
"version": "1.0.0"
}
],
"deployment_id": "prod-2026-01-26"
}
Response: 201 Created
{
"deployment_id": "prod-2026-01-26",
"project_bundles": ["bundle:prior-auth:1.0.0"],
"resolved_bundles": [
"bundle:meta:1.0.0",
"bundle:hipaa:1.0.0",
"bundle:acme-health-corp:1.0.0",
"bundle:prior-auth:1.0.0"
],
"status": "active",
"created_at": "2026-01-26T14:00:00Z"
}
Get Deployment¶
GET /api/deployments/{deployment_id}
Response: 200 OK
List Deployments¶
GET /api/deployments?status=active
Response: 200 OK
Audit API¶
Verify Audit Chain Integrity¶
Walks the entire hash-chained audit log and recomputes every record hash.
GET /api/audit/verify
X-API-Key: sk_your_api_key_here
Response: 200 OK
{
"status": "ok",
"record_count": 1042,
"legacy_record_count": 0,
"first_chained_sequence": 1,
"last_verified_at": "2026-04-19T10:30:00Z",
"tampered_at_sequence": null,
"error": null
}
If tampering is detected:
{
"status": "tampered",
"record_count": 1042,
"legacy_record_count": 0,
"first_chained_sequence": 1,
"last_verified_at": "2026-04-19T10:30:00Z",
"tampered_at_sequence": 214,
"error": null
}
Authentication: Admin API key required (X-API-Key header).
Telemetry API¶
Telemetry endpoints are served on port 8004 (not 8000).
Get Telemetry Config¶
GET /config
Response: 200 OK
{
"enabled": false,
"endpoint": null,
"deployment_id": "uuid-here",
"daily_report_hour": 0,
"retry_max_attempts": 5,
"retry_backoff_base": 60
}
Get Telemetry Stats¶
GET /stats
Response: 200 OK
{
"total_reports_sent": 12,
"total_reports_failed": 0,
"last_report_at": "2026-04-19T00:00:00Z",
"queue_depth": 0
}
Get Telemetry Log¶
GET /log?limit=50&offset=0
Query Parameters:
- limit — max entries to return (default: 50)
- offset — pagination offset
Response: 200 OK
[
{
"id": 1,
"reported_at": "2026-04-19T00:00:00Z",
"status": "sent",
"agent_count": 5,
"context_request_count": 120,
"bundle_count": 3
}
]
Trigger Manual Report¶
POST /trigger
Forces an immediate telemetry report outside the daily schedule.
Response: 200 OK
{
"status": "triggered"
}
Rules Engine API¶
Create Rule Schema¶
POST /api/rules/schemas
Content-Type: application/json
{
"id": "schema:formulary",
"schema": {
"type": "object",
"properties": {
"drug_name": {"type": "string"},
"tier": {"type": "integer"},
"requires_pa": {"type": "boolean"}
},
"required": ["drug_name", "tier"]
}
}
Response: 201 Created
Create Rule Set¶
POST /api/rules/rule-sets
Content-Type: application/json
{
"id": "ruleset:formulary:acme",
"version": "2026-Q1",
"schema_id": "schema:formulary"
}
Response: 201 Created
Add Rule to Rule Set¶
POST /api/rules/rule-sets/{rule_set_id}/rules
Content-Type: application/json
{
"rule_id": "rule:lisinopril",
"rule_data": {
"drug_name": "Lisinopril",
"tier": 1,
"requires_pa": false
}
}
Response: 201 Created
List Rule Sets¶
GET /api/rules/rule-sets
Response: 200 OK
Activity Ingestion API (Collector Store)¶
This is a deliberately separate, standalone API — not part of the control-plane FastAPI app
above, not on port 8000, and not behind X-Admin-Key. It's a minimal stdlib-only Python HTTP
server (integrations/cowork-telemetry/store/app.py) reading from the SQLite database that the
three activity collectors (Cowork OTel, Claude Code CLI OTel, Compliance API poller) write into.
Read by the dashboard's Agent Activity tab as a second, independently-configured endpoint. See
Data Dictionary for what each collector actually captures and redacts.
Base URL: http://<collector-host>:7002
Health Check¶
GET /health
200 OK — {"status": "ok"}
List Observed Activity¶
GET /api/activity/observed?limit=50
limit is clamped to 1–500 (default 50). Returns the most recent rows from
observed_activity_log, newest first — every column in the table (see Data Dictionary),
including raw_attributes (the full redacted attribute map, for drill-down).
List Activity Sources¶
GET /api/activity-sources
claude-enterprise-cowork, claude-code-cli,
claude-enterprise-chat): source_id, description, event_count, first_event_at,
last_event_at.
Get Source Coverage Manifest¶
GET /api/activity-sources/{source_id}/coverage
captures / does_not_capture / caveats manifest —
generated by the collector itself (COVERAGE_MANIFEST in each watcher/poller), not a hand-written
doc that can drift from what's actually wired up.
Activity Stats¶
GET /api/activity/stats
200 OK
{
"total_events": 412,
"unique_users": 3,
"unique_tools": 7,
"fields_redacted": 89
}
Error Responses¶
400 Bad Request¶
{
"error": "validation_error",
"message": "Invalid bundle type",
"details": { ... }
}
401 Unauthorized¶
{
"error": "auth_failed",
"message": "Invalid or missing API key"
}
403 Forbidden¶
{
"error": "intent_not_allowed",
"message": "Task type not in agent's allowed intents"
}
404 Not Found¶
{
"error": "not_found",
"message": "Agent not found: agent:xyz"
}
409 Conflict¶
{
"error": "conflict",
"message": "Bundle version already exists (immutable)"
}
500 Internal Server Error¶
{
"error": "internal_error",
"message": "An unexpected error occurred"
}
API Status¶
| Endpoint Group | Status | Notes |
|---|---|---|
| Context API (v0.3) | ✅ Implemented | API key auth, intent validation, role-based context, optional full content |
| Agent Registry | ✅ Implemented | CRUD, API keys, revocation, lifecycle enforcement, HITL owner field |
| MCP Server Registry | ✅ Implemented | CRUD, HITL owner field — same pattern as Agent Registry |
| Bundle Registry | ✅ Implemented | CRUD, versioning, import resolution |
| Tag Management | ✅ Implemented | Create, update, resolve tags |
| Deployment | ✅ Implemented | Deploy, list, get deployments |
| Rules Engine | ✅ Implemented | Schemas, rule sets, rules |
| Audit API | ✅ Implemented | Hash-chained audit log, chain integrity verification |
| Telemetry API | ✅ Implemented | Usage stats, log, manual trigger (port 8004) |
| Usage/Adherence | 🚧 Partial | Tables exist, analytics TBD |
| Activity Ingestion (Collector Store) | ✅ Implemented | Separate standalone API, port 7002, no admin-key auth — see above |
gRPC API¶
For streaming context, use the gRPC API on port 8002.
RequestContext (v0.3)¶
rpc RequestContext(ContextRequest) returns (ContextResponse);
message ContextRequest {
string api_key = 1;
string task_type = 2;
map<string, string> params = 3;
}
message ContextResponse {
bool success = 1;
string request_id = 2;
string agent_id = 3;
string role_id = 4;
string task_type = 5;
repeated string scd_ids = 6;
map<string, string> scd_content = 7;
int64 timestamp = 8;
int32 processing_time_ms = 9;
string error_code = 10;
string error_message = 11;
}
Subscribe (v0.2)¶
rpc Subscribe(SubscribeRequest) returns (stream ContextMessage);
message SubscribeRequest {
string tag = 1;
string agent_id = 2;
string auth_token = 3;
}
Next Steps¶
- See CLI Commands for bundle authoring
- See Architecture Overview for system design
- See Getting Started for quick start