API Contract
The cPod backend exposes a REST API documented as an OpenAPI 3.1 specification. The spec is the authoritative source of truth for every endpoint, request/response shape, and error code in the platform.
SDK vs direct API. Use the typed SDKs (@cpod/sdk, cpod-sdk, etc.) rather than
calling the REST API directly. The spec exists to define the contract between SDK and
backend — not as a primary integration point for application developers.
Emulator vs Production Backend#
The cPod emulator is a fully-functional stub backend built to serve the same REST contract
as the production cpod-backend. During development you point the SDK at the emulator;
in production you point it at https://api.cyberpod.app. The contract is identical — the
only difference is where the data lives and how tokens are issued.
| Emulator | Production | |
|---|---|---|
| Base URL | http://localhost:4000 | https://api.cyberpod.app |
| Auth token endpoint | http://localhost:4000/api/v1/auth/token | https://api.cyberpod.app/api/v1/auth/token |
| Token issuance | Any client_id / client_secret accepted | Real OAuth 2.0 credentials required |
| Data persistence | In-memory (reset on restart) | Durable PostgreSQL + S3 |
| Interactive docs | http://localhost:4000/docs | — |
Switching base URL in the SDK:
import { CpodClient } from '@cpod/sdk'
// Development — emulator
const dev = new CpodClient({
apiKey: 'any-key',
baseUrl: 'http://localhost:4000',
})
// Production
const prod = new CpodClient({
apiKey: process.env.CPOD_API_KEY!,
// baseUrl defaults to https://api.cyberpod.app
})from cpod import CpodClient
# Development — emulator
dev = CpodClient(api_key="any-key", base_url="http://localhost:4000")
# Production
prod = CpodClient.from_env() # reads CPOD_BASE_URL and CPOD_API_KEYimport cpod "github.com/cpod-ai/cpod-sdk-go"
// Development — emulator
dev := cpod.New("any-key", cpod.WithBaseURL("http://localhost:4000"))
// Production
prod := cpod.New(os.Getenv("CPOD_API_KEY"))using Cpod.SDK;
// Development — emulator
var dev = new CpodClient("any-key", baseUrl: "http://localhost:4000");
// Production
var prod = new CpodClient(Environment.GetEnvironmentVariable("CPOD_API_KEY")!);Set CPOD_BASE_URL=http://localhost:4000 in your .env file. All four SDKs pick this up automatically via from_env() / CpodClient.from_env() / fromEnv().
Authentication#
All endpoints except /api/v1/auth/token and /health require a Bearer token.
Obtaining a token#
curl -s http://localhost:4000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"client_id":"dev","client_secret":"dev","grant_type":"client_credentials"}'
# → {"access_token":"eyJ...","token_type":"Bearer","expires_in":3600}The emulator accepts any client_id and client_secret values.
curl -s https://api.cyberpod.app/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "<your-client-id>",
"client_secret": "<your-client-secret>",
"grant_type": "client_credentials"
}'
# → {"access_token":"eyJ...","token_type":"Bearer","expires_in":3600}Obtain credentials by registering an app via /api/v1/apps/register or from the cPod console.
Using the token#
GET /v1/people HTTP/1.1
Host: api.cyberpod.app
Authorization: Bearer eyJ...
Accept: application/jsonThe SDKs handle token acquisition and refresh automatically when you provide apiKey (SDK shorthand for the OAuth client secret).
Domain → API Prefix Mapping#
Every EDM domain has a dedicated base path. All entity CRUD operations follow the pattern
GET|POST /v1/{prefix}, GET|PATCH|DELETE /v1/{prefix}/{id}.
| Domain | API Prefix | Entities |
|---|---|---|
| People | /v1/people | Person |
| Groups | /v1/groups | Group |
| Technology | /v1/technology | TechnologyAsset, AccessEntitlement |
| Licenses | /v1/licenses | SoftwareLicense, LicenseAssignment |
| Assets | /v1/assets | PhysicalAsset, CloudResource |
| Risk | /v1/risk | Vulnerability, ComplianceControl, RiskItem |
| Relationships | /v1/relationships | Relationship |
| DataSources | /v1/datasources | DataSource |
| Contracts | /v1/contracts | Vendor, Contract, ContractObligation |
| Employee | /v1/employee | PersonSkill, CalendarEvent, LeaveRequest, MeetingNote |
| Work | /v1/work | TimeEntry, Comment, Capacity |
| Performance | /v1/performance | PerformanceReview, PersonGoal, LearningRecord |
| Investments | /v1/investments | TechPortfolioItem, CostCenter |
| Customer | /v1/customer | Account, Contact, Deal, Activity |
| Projects | /v1/projects | Project, Task, Sprint, Feature |
| OKR | /v1/okr | Objective, KeyResult |
| GRC | /v1/grc | GrcFramework, GrcControl, GrcEvidence, GrcIncident, GrcRisk |
| SOC | /v1/soc | SocAlert, SocInvestigation, SocPlaybook |
| Telemetry | /v1/telemetry | AuditEvent, AppEvent, Trace |
| Finance | /v1/finance | Invoice, PurchaseOrder, Expense, Budget, BudgetLine |
| HR | /v1/hr | JobPosting, Applicant, OnboardingTask, OffboardingTask |
| Helpdesk | /v1/helpdesk | Ticket, SlaPolicy |
| Notifications | /v1/notifications | Notification, Announcement |
| Approvals | /v1/approvals | ApprovalRequest, ApprovalStep |
| Org | /v1/org | Location, Department |
| Catalog | /v1/catalog | Product, ProductCategory |
| Procurement | /v1/procurement | Supplier |
| Knowledge | /v1/knowledge | Document, Chunk, KnowledgeEntity |
| Learning | /v1/learning | Cohort, Assessment |
| RFP | /v1/rfp | RfpRecord, RfpQuestion, RfpResponse |
| Integration | /v1/integration | Application, Connector, ApiKey, Webhook |
| Storage | /v1/storage | StorageObject, StorageRecord |
Standard Request/Response Envelope#
Single-entity response (ApiResponse<T>)#
Every endpoint that returns a single entity wraps it in this shape:
{
"data": { /* entity object */ },
"meta": {
"requestId": "req-01J...",
"timestamp": "2026-05-22T10:00:00.000Z"
}
}List response (PaginatedResponse<T>)#
List endpoints (GET /v1/{prefix}) return:
{
"data": [ /* entity objects */ ],
"total": 1024,
"page": 1,
"pageSize": 50,
"hasMore": true,
"meta": {
"requestId": "req-01J...",
"timestamp": "2026-05-22T10:00:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
data | T[] | Array of entities for this page |
total | integer | Total matching record count across all pages |
page | integer | Current page number (1-based) |
pageSize | integer | Number of items per page |
hasMore | boolean | true when page * pageSize < total |
meta.requestId | string | Traceable request ID for support |
meta.timestamp | string | UTC ISO 8601 timestamp of the response |
Mutation request body#
Create and update requests send a JSON body with Content-Type: application/json.
Only the fields you include are applied (partial update / PATCH semantics for updates).
{
"firstName": "Alice",
"lastName": "Smith",
"email": "alice@acme.com",
"type": "employee"
}Error Codes#
All errors follow RFC 7807 Problem Detail format:
{
"title": "Validation Error",
"status": 422,
"detail": "email must be a valid email address",
"instance": "/v1/people",
"requestId": "req-01J..."
}| Status | Title | Typical Cause |
|---|---|---|
400 | Bad Request | Malformed JSON, missing required fields |
401 | Unauthorized | Missing, expired, or invalid Bearer token |
403 | Forbidden | Valid token but insufficient scope/permissions |
404 | Not Found | Entity ID does not exist in this tenant |
422 | Unprocessable Entity | Payload passes schema validation but fails business rules (e.g. duplicate email) |
429 | Too Many Requests | Rate limit exceeded — back off and retry after Retry-After header |
500 | Internal Server Error | Unexpected backend failure — contact support with requestId |
Specification File#
The OpenAPI 3.1 spec lives at
docs/api-contract/openapi.yaml.
All schemas use JSON Schema Draft 2020-12 with additionalProperties: false.
View interactively#
npx @redocly/cli preview-docs docs/api-contract/openapi.yaml
# Opens http://localhost:8080npx @cpod/emulator --ui
# Opens http://localhost:4000/docsnpx @stoplight/elements-dev-portal
# Point it at docs/api-contract/openapi.yamlContract Validation (CI)#
Every PR touching docs/api-contract/, emulator/src/routes/, or sdks/ triggers the
API Contract Validation workflow (.github/workflows/api-contract.yml), which runs:
1. Spectral lint#
| Rule | Severity | Description |
|---|---|---|
operation-operationId | error | Every operation must have a unique operationId |
operation-description | warn | Every operation should include a description |
operation-tags | warn | Every operation should belong to a tag |
operation-success-response | error | Every operation must define at least one 2xx response |
oas3-schema | error | Schema objects must be valid OAS 3.1 |
cpod-id-pattern | warn | Entity id properties must declare a regex pattern |
cpod-no-inline-schemas | warn | Inline schemas with >3 properties should be extracted to components/schemas |
cpod-pagination-shape | warn | List-endpoint responses should include items and total |
2. Path completeness check#
scripts/check-api-contract.mjs asserts ≥ 40 paths, ≥ 10 component schemas, OpenAPI 3.1.x,
info.contact present, and every path starting with /v1/.
3. SDK alignment check#
scripts/validate-api-contract.ts compares SDK service names against OpenAPI tags and fails
if any service has no corresponding tag (or vice-versa).
Keeping the Contract in Sync#
When you add a new endpoint, follow this order:
- Emulator route — add the handler in
emulator/src/routes/ - OpenAPI spec — add the path + schema to
docs/api-contract/openapi.yaml - SDK methods — implement the client method in all four SDKs
- Tests — add at minimum a smoke test per SDK
- Docs — update the relevant domain page under
docs/pages/docs/domains/
Never hand-edit the spec for an endpoint that does not yet exist in the emulator. The spec and the emulator must stay in lock-step — they are tested together in CI.
Entity ID Prefixes#
All cPod entity IDs use a short kebab prefix so they are self-describing in logs:
| Entity | Prefix | Example |
|---|---|---|
| Person | per- | per-01J... |
| Group | grp- | grp-01J... |
| TechnologyAsset | tast- | tast-01J... |
| AccessEntitlement | ent- | ent-01J... |
| SoftwareLicense | lic- | lic-01J... |
| LicenseAssignment | lasgn- | lasgn-01J... |
| PhysicalAsset | past- | past-01J... |
| CloudResource | cres- | cres-01J... |
| Vulnerability | vuln- | vuln-01J... |
| ComplianceControl | ctrl- | ctrl-01J... |
| RiskItem | risk- | risk-01J... |
| Relationship | rel- | rel-01J... |
| DataSource | ds- | ds-01J... |
| Vendor | vnd- | vnd-01J... |
| Contract | ctr- | ctr-01J... |
| ContractObligation | obl- | obl-01J... |
The cpod-id-pattern Spectral rule warns when an id property is missing a pattern
constraint, keeping the spec honest about ID shapes.