Skip to Content
API Reference

API Reference

Pre-beta API: Routes match api.cendriix.ai/v1 as of June 2026. Auth uses a Cognito JWT Bearer token; workspace scope comes from the token. Rate limits are enforced globally.

Authentication

curl https://api.cendriix.ai/v1/runs \ -H "Authorization: Bearer <cognito-jwt>"

API keys via POST /api-keys are available but JWT auth is expected in preview.

Base URL and rate limits

https://api.cendriix.ai/v1
PlanRequests / minuteRequests / day
Starter605,000
Growth300100,000
EnterpriseUnlimitedUnlimited

Rate-limit headers: X-RateLimit-Remaining, X-RateLimit-Reset (Unix seconds).


Identity

POST /auth/login

Exchange an OAuth profile for a signed JWT.

Body:

{ "provider": "github | google", "oauthId": "string", "email": "user@example.com", "displayName": "string", "avatarUrl": "https://..." }

Response: { "token": "<JWT>", "user": { ... } }

GET /auth/me

Returns the authenticated user’s profile with id, email, displayName, avatarUrl, workspaces.

POST /workspaces

Create a new workspace.

Body: { "name": "string", "plan": "starter" | "team" | "enterprise" }

Response (201): Workspace object.

GET /workspaces

List workspaces the authenticated user belongs to.

GET /workspaces/:workspaceId

Get a single workspace by ID.

POST /workspaces/:workspaceId/members

Invite a member to a workspace.

Body: { "email": "string", "role": "owner" | "admin" | "member" | "viewer" }

GET /workspaces/:workspaceId/members

List all members of a workspace.

POST /workspaces/:workspaceId/switch

Re-issue a JWT scoped to the specified workspace.

POST /api-keys

Create a new API key for the workspace.

Body: { "name": "string", "expiresAt": "ISO-8601" }

Response (201): { "apiKey": { ... }, "rawKey": "<plain-text-key>" } — the raw key is returned once and never stored in plain text.

GET /api-keys

List all API keys for the workspace (metadata only).

DELETE /api-keys/:keyId

Revoke an API key. Response: 204 No Content.


Orchestrator (Runs)

POST /runs

Create a new run. The orchestrator immediately decomposes the input into a RunPlan.

Body:

{ "workflowId": "uuid", "input": { "title": "string", "body": "string" } }

Response (201): Run object with id, tenantId, workflowId, status: "planned", plan: { steps: [...] }.

GET /runs

List all runs for the workspace.

GET /runs/:runId

Get a single run including its full plan.

POST /runs/:runId/advance

Advance a planned run into execution.

POST /runs/:runId/approve

Approve a run paused at an approval gate.

Body: { "note": "LGTM" }

POST /runs/:runId/cancel

Cancel an in-progress run. Agents are stopped gracefully.

POST /runs/:runId/retry

Retry a failed or cancelled run. Optionally resume from a specific step.

Body: { "fromStepId": "step-3" } (optional)

Response (202): Updated run object.

GET /runs/:runId/lifecycle

Full lifecycle stage walkthrough for a run — each phase with a reached and current flag.

POST /orchestrator/decompose

Decompose a ticket into an enriched RunPlan without creating a run. Returns estimatedCostUsd, reversibilityTier, and confidenceScore per step.

Body: { "workflowId": "uuid", "input": { "title": "string", "body": "string" } }

POST /orchestrator/estimate

Re-cost an edited RunPlan. Submit a modified plan and receive refreshed cost estimates.

Body: { "plan": { "steps": [...], "generatedAt": "ISO-8601" } }

POST /runs/:runId/promote-playbook

Promote a successfully-merged run into a reusable playbook.

Pre-conditions: Run must be in merged state.

Response (201): { "playbook": { ... } }


Agent Runtime

POST /agent/steps

Execute a single agent step.

Body:

{ "runId": "string", "name": "string", "prompt": "string", "systemPrompt": "string", "allowedConnectorIds": ["connector-id"], "policy": { "maxIterations": 5, "tokenBudget": 16000, "taskType": "code_generation | reasoning | summarisation | classification | general", "maxTokensPerCall": 2048, "toolTimeoutMs": 30000 } }

Response (201): AgentStep object with id, runId, status, turns, stopReason, totalTokens.

GET /agent/steps/:stepId

Get a single agent step by ID.

GET /agent/runs/:runId/steps

List all steps for a run.


Model Router

POST /model-router/complete

Execute an inference call through the routing policy. The router ranks candidates and dispatches via the appropriate provider adapter.

Body:

{ "prompt": "string", "taskType": "code_generation | reasoning | summarisation | classification | general", "maxTokens": 2048, "systemPrompt": "string", "requiredCapabilities": ["code"], "policyHint": "cost | latency", "maxCostUsd": 0.10 }

Response (200): { "modelId": "...", "provider": "...", "response": "...", "inputTokens": N, "outputTokens": N, "latencyMs": N, "estimatedCostUsd": N, "usedFallback": bool }

GET /model-router/models

Returns the model catalogue grouped by source (orchestrated, bedrock, cendriix-custom, native).

GET /model-router/health

Circuit-breaker snapshots for all models.

GET /model-router/routing-health

Tier A/B/C routing-health heatmap per (taskType, modelId).


MCP Gateway (Connectors)

GET /connectors

List all MCP connectors registered for the workspace.

POST /connectors

Register a new MCP connector.

Body:

{ "name": "string", "endpointUrl": "https://...", "authType": "api_key | oauth", "credentials": { ... } }

DELETE /connectors/:id

Delete a connector. Response: 204 No Content.

GET /mcp/tools

List all tools available across all connectors.

Response: { "tools": [{ "connectorId": "...", "name": "...", "description": "...", "inputSchema": { ... } }] }

POST /mcp/execute

Execute a tool call directly (outside of an agent loop).

Body: { "connectorId": "string", "toolName": "string", "input": { ... } }

Response: { "output": ..., "isError": false, "latencyMs": 120 }


Workflows

GET /workflows

List all workflows for the workspace.

POST /workflows

Create a new workflow (DAG of nodes and edges).

GET /workflows/:id

Get a workflow by ID.

PUT /workflows/:id

Update a workflow.

DELETE /workflows/:id

Delete a workflow. Response: 204 No Content.

POST /workflows/validate

Validate a workflow definition without saving.

Response: { "valid": true } or { "valid": false, "errors": [...] }

POST /workflows/execute

Execute a workflow with a given input, creating a run.

Body: { "workflowId": "uuid", "input": { ... } }

Response (202): Run object.

GET /workflows/templates

List all workflow templates.

POST /workflows/from-template/:idOrSlug

Instantiate a workflow from a template.

Body: { "name": "string", "costCapUsd": 5.00 }

POST /workflows/compose

Compose a DAG from a free-form intent string using AI.

Body: { "intent": "string" }

Response: { "proposal": { ... } } or { "proposal": null, "emptyState": "no_entities" }

POST /workflows/compose/approve

Persist a composed proposal as a real workflow.

GET /workflow-runs/:runId/blast-radius/:nodeId

Resolve the infrastructure blast-radius for a workflow node.

GET /workflow-runs/:runId/stream

SSE live execution stream. Events: NODE_RUN_STARTED, NODE_RUN_COMPLETED, NODE_RUN_FAILED, RUN_COMPLETED.


A2A Gateway (Agent-to-Agent)

GET /.well-known/agent-card.json

Signed Agent Card discovery (A2A v1.0). No authentication required.

GET /a2a/agent-card

Tenant fleet card — all enabled agents.

POST /a2a/tokens

Issue a short-lived, agent-bound, skill-scoped A2A bearer token.

Body: { "agentId": "uuid", "skills": ["skill-id"], "ttlSeconds": 3600 }

POST /a2a/jsonrpc

JSON-RPC 2.0 dispatch for all A2A methods: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask.

GET /a2a/tasks/:id/stream

SSE task streaming.

GET /a2a/topology

Live agent-to-agent delegation topology.


Conversations (Cendra Chat)

POST /conversations

Create a new conversation session.

GET /conversations

List conversation sessions.

GET /conversations/:id

Get a session with message history.

POST /conversations/:id/messages

Send a message. Routes to capability C1–C4 based on intent.

Body:

{ "content": "string", "context": { "pathname": "/runs", "focusedRunId": "uuid" } }

POST /conversations/:id/hitl-decide

Submit an approve/deny decision for an approval gate surfaced in chat.

Body: { "subjectId": "uuid", "decision": "approve | deny", "reason": "optional" }

POST /conversations/:id/cost-confirm

Accept a cost-confirmation card and dispatch the run.

Body: { "workflowId": "uuid" }


Security

POST /security/scans

Trigger a security scan on a run.

Body: { "runId": "string", "types": ["sast", "secrets", "dependency"] }

GET /security/scans/run/:runId

Get all scan results for a run.

GET /security/policy

Get the workspace’s guardrail policy.

PUT /security/policy

Upsert the guardrail policy.

Body: { "rules": [{ "name": "string", "condition": { ... }, "action": "block | warn" }] }

POST /security/evaluate

Evaluate an agent action against the current policy.

Response: { "allowed": true | false, "violations": [...] }


Metering & Cost

GET /metering/usage

Aggregate token usage for the current billing period.

Response: { "usage": { "inputTokens": N, "outputTokens": N, "estimatedCostUsd": N, "byModel": { ... } } }

GET /metering/runs/:runId/costs

Per-step cost breakdown for a run.

GET /metering/runs/:runId/cap-status

Check whether a run has exceeded any budget cap.

GET /metering/runs/:runId/budget

Get budget configuration for a run.

PUT /metering/runs/:runId/budget

Set or update budget caps.

Body: { "caps": { "tokenBudget": 50000, "maxCostUsd": 1.00 } }


Audit

GET /audit/logs

Query the audit log with filters: actor, eventType, from, to, limit, cursor.

Response: { "entries": [{ "id": "...", "timestamp": "...", "actor": "...", "eventType": "...", "payload": { ... }, "hash": "..." }] }

GET /audit/verify

Verify the integrity of the audit log hash chain.

Response: { "valid": true, "checkedCount": N }


Billing

GET /billing/plans

List available subscription plans. No authentication required.

POST /billing/subscriptions

Create a new subscription (requires owner or admin role).

GET /billing/subscriptions

Get the workspace’s current subscription.

GET /billing/invoices

List invoices (requires owner or admin role).


Sandboxes

POST /sandboxes

Provision a new sandbox environment.

Body: { "runtimeType": "string", "resourceLimits": { "cpu": "2", "memory": "4Gi" } }

Response (202): Sandbox object with status: "provisioning".

GET /sandboxes

List all sandboxes for the workspace.

GET /sandboxes/:id

Get a sandbox by ID.

DELETE /sandboxes/:id

Terminate and destroy a sandbox. Response: 202 Accepted.


Notifications

GET /notifications

List in-product notifications.

POST /notifications/:id/read

Mark a notification as read.

POST /notifications/read-all

Mark all notifications as read.

GET /notifications/preferences

Get notification delivery preferences.

PUT /notifications/preferences

Update preferences.


Entities (Skills, Plugins, Playbooks, Policies, Guardrails)

A unified store for all buildable entity kinds.

GET /entities

List entities. Filter by kind, origin, q (free-text).

POST /entities

Create an entity.

Body: { "kind": "skill | plugin | playbook | policy | guardrail", "name": "string", "spec": { ... } }

GET /entities/:id

Get an entity.

PATCH /entities/:id

Update an entity (re-validates the spec).

DELETE /entities/:id

Delete an entity. Response: 204 No Content.

POST /entities/:id/fork

Copy-on-write into a new entity.

POST /entities/:id/publish

Snapshot the current definition as an immutable version.

GET /entities/:id/versions

Version history.

POST /entities/:id/validate

Dry-run validation.

GET /entities/:id/linkage

Resolve the reference graph for an entity.


Projects

GET /projects

List the workspace’s projects.

POST /projects

Create a project from a plain-English idea.

Body: { "name": "string", "idea": "string", "cloud": "cendriix-managed", "region": "us-east-1", "tags": ["..."] }

GET /projects/:id

Full project detail — status, concerns, cost, linked run.

PATCH /projects/:id/status

Advance or roll back the project lifecycle.

Body: { "status": "idea | provisioning | building | live | paused | archived" }


Proactive Intelligence

GET /proactive/signals

Current signal buffer (anomalies, cost spikes, stalled runs, pending gates).

GET /proactive/signals/stream

SSE stream of real-time proactive signals.


Briefing (Concierge)

GET /briefing/digest

Structured briefing digest with grounded LLM narration.

Query: days (default 7, max 90).

GET /briefing/decisions

Pending decision items (the action strip in the Concierge home).


Voice

POST /voice/tts

Synthesise speech from text.

Body: { "text": "string" }

Response: { "audioBase64": "...", "voiceId": "..." }

POST /voice/transcribe

Transcribe audio to text.

Body: { "audioBase64": "..." }

Response: { "transcript": "...", "confidence": N }

GET /voice/session/nova

WebSocket upgrade for real-time bidirectional audio.


Errors

StatusErrorDescription
400bad_requestMalformed request or missing fields
401unauthorizedMissing or invalid JWT
403forbiddenValid token but insufficient permission
404not_foundResource does not exist
409conflictRun state does not allow this action
422validation_errorZod validation failure — structured fieldErrors body
429rate_limit_exceededCheck X-RateLimit-Reset
500internal_errorInclude requestId when reporting
Last updated on