API Reference
Pre-beta API: Routes match
api.cendriix.ai/v1as 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| Plan | Requests / minute | Requests / day |
|---|---|---|
| Starter | 60 | 5,000 |
| Growth | 300 | 100,000 |
| Enterprise | Unlimited | Unlimited |
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
| Status | Error | Description |
|---|---|---|
400 | bad_request | Malformed request or missing fields |
401 | unauthorized | Missing or invalid JWT |
403 | forbidden | Valid token but insufficient permission |
404 | not_found | Resource does not exist |
409 | conflict | Run state does not allow this action |
422 | validation_error | Zod validation failure — structured fieldErrors body |
429 | rate_limit_exceeded | Check X-RateLimit-Reset |
500 | internal_error | Include requestId when reporting |