REST API Reference
Complete reference for the RootCX Core HTTP API.
Overview
| Property | Value |
|---|---|
| Base URL | https://<your-ref>.rootcx.com (or http://localhost:9100 if self-hosting) |
| Protocol | HTTP/1.1 |
| Format | JSON (UTF-8) |
| Max body | 50 MB |
All /api/v1/* routes accept and return JSON. The deploy endpoint accepts multipart/form-data with an archive field (tar.gz). The logs and agent invoke endpoints return text/event-stream.
Authentication
Pass a JWT access token as a Bearer token in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
All /api/v1/* endpoints require a valid token except auth endpoints (register, login, mode, oidc/providers list). Requests to protected endpoints without a token receive a 401 Unauthorized response.
System
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check -- returns {status:'ok'} |
| GET | /api/v1/status |
Runtime status |
Auth
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Register a new user |
| POST | /api/v1/auth/login |
Login and receive tokens |
| POST | /api/v1/auth/refresh |
Refresh an expired access token |
| POST | /api/v1/auth/logout |
Invalidate session |
| GET | /api/v1/auth/me |
Get current authenticated user |
| GET | /api/v1/auth/mode |
Get current auth mode (public) |
| GET | /api/v1/users |
List all users |
| DELETE | /api/v1/users/{id} |
Delete a user |
POST /api/v1/auth/register
// Request
{
"email": "alice@example.com",
"password": "Str0ngPass!x",
"displayName": "Alice Martin"
}
// Response 201
{
"user": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"email": "alice@example.com",
"displayName": "Alice Martin",
"createdAt": "2024-01-15T10:30:00+00:00"
}
}
Password requirements: minimum 10 characters, at least one uppercase letter, one lowercase letter, and one digit.
admin role. Registration does not return tokens -- call the login endpoint after.GET /api/v1/auth/mode
{
"authRequired": true,
"setupRequired": false,
"passwordLoginEnabled": true,
"providers": [
{ "id": "okta", "displayName": "Okta SSO" }
]
}
POST /api/v1/auth/login
// Request
{ "email": "alice@example.com", "password": "Str0ngPass!x" }
// Response 200
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"expiresIn": 900,
"user": {
"id": "3fa85f64-...",
"email": "alice@example.com",
"displayName": "Alice Martin",
"createdAt": "2024-01-15T10:30:00+00:00"
}
}
DELETE /api/v1/users/{id}
Delete a user. Returns 400 if this is the last admin (prevents lockout).
OIDC
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/auth/oidc/providers |
List enabled providers (public) |
| POST | /api/v1/auth/oidc/providers |
Create/update provider (admin) |
| DELETE | /api/v1/auth/oidc/providers/{id} |
Delete provider (admin, ?force=true) |
| GET | /api/v1/auth/oidc/{provider_id}/authorize |
Start browser auth flow (?redirect_uri=...) |
| GET | /api/v1/auth/oidc/callback |
Authorization callback |
| POST | /api/v1/auth/oidc/token-exchange |
Server-to-server token exchange |
See Authentication for provider configuration details.
POST /api/v1/auth/oidc/token-exchange
// Request
{ "providerId": "okta", "idToken": "eyJhbGciOiJSUzI1..." }
// Response 200
{ "accessToken": "eyJ...", "expiresIn": 900 }
Apps
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/apps |
List all installed applications |
| POST | /api/v1/apps |
Install an application from a manifest |
| DELETE | /api/v1/apps/{appId} |
Uninstall an application (cleans secrets, jobs, filesystem) |
POST /api/v1/apps -- Install app
// Request body: AppManifest object
{
"appId": "crm",
"name": "My CRM",
"version": "1.0.0",
"dataContract": [...],
"permissions": {...}
}
// Response 200
{ "message": "app 'crm' installed" }
Data (CRUD)
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/apps/{appId}/collections/{entity} |
List all records |
| POST | /api/v1/apps/{appId}/collections/{entity} |
Create a record |
| GET | /api/v1/apps/{appId}/collections/{entity}/{id} |
Get a record by ID |
| PATCH | /api/v1/apps/{appId}/collections/{entity}/{id} |
Partially update a record |
| DELETE | /api/v1/apps/{appId}/collections/{entity}/{id} |
Delete a record |
Unknown entities return 404 (not 500). System columns id, created_at, updated_at are auto-managed.
Record shape
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"first_name": "Alice",
"last_name": "Martin",
"email": "alice@acme.com",
"tags": ["vip", "enterprise"],
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
Workers
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/apps/{appId}/deploy |
Deploy worker code (multipart, archive field, tar.gz) |
| POST | /api/v1/apps/{appId}/worker/start |
Start the worker process |
| POST | /api/v1/apps/{appId}/worker/stop |
Stop the worker process |
| GET | /api/v1/apps/{appId}/worker/status |
Get worker status |
| POST | /api/v1/apps/{appId}/rpc |
Invoke a worker RPC method |
| SSE | /api/v1/apps/{appId}/logs |
Stream live worker logs |
POST /api/v1/apps/{appId}/rpc
// Request
{ "method": "sendWelcomeEmail", "params": { "contactId": "f47ac10b..." } }
// Response 200 -- the handler's return value is returned directly
{ "sent": true }
RPC timeout: 30 seconds.
Worker status values
starting, running, stopping, stopped, crashed.
Jobs
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/apps/{appId}/jobs |
Enqueue a background job |
| GET | /api/v1/apps/{appId}/jobs |
List active jobs (?archived=true for completed) |
Jobs are backed by pgmq with a 120-second visibility timeout. Unacknowledged jobs are automatically re-queued.
POST /api/v1/apps/{appId}/jobs
// Request
{ "payload": { "type": "send_report", "userId": "..." } }
// Response 201
{ "msg_id": 42 }
Job object
{
"msg_id": 42,
"app_id": "crm",
"payload": { "type": "send_report", "userId": "..." },
"user_id": "3fa85f64-...",
"read_ct": 1,
"enqueued_at": "2024-01-15T10:30:00Z"
}
Crons
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/apps/{appId}/crons |
Create a scheduled cron |
| GET | /api/v1/apps/{appId}/crons |
List all crons for an app |
| PATCH | /api/v1/apps/{appId}/crons/{id} |
Update a cron |
| DELETE | /api/v1/apps/{appId}/crons/{id} |
Delete a cron |
| POST | /api/v1/apps/{appId}/crons/{id}/trigger |
Fire a cron immediately |
Crons are backed by pg_cron. Each tick enqueues a job into the same pgmq queue used by one-shot jobs, dispatched to the worker's onJob handler.
POST /api/v1/apps/{appId}/crons
// Request
{
"name": "daily-check",
"schedule": "0 9 * * *",
"timezone": "America/New_York",
"payload": { "task": "check_replies" },
"overlapPolicy": "skip"
}
// Response 201
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"appId": "crm",
"name": "daily-check",
"schedule": "0 9 * * *",
"timezone": "America/New_York",
"payload": { "task": "check_replies" },
"overlapPolicy": "skip",
"enabled": true,
"pgJobId": 7,
"createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
Schedule formats:
- Standard 5-field cron:
* * * * *(minute hour day-of-month month day-of-week) - Seconds interval:
10 seconds(1-59, cannot combine with cron fields) $in day-of-month = last day of the month
All times are GMT unless timezone is set.
Overlap policy: "skip" (default) drops the job if a previous one is still queued. "queue" enqueues regardless.
PATCH /api/v1/apps/{appId}/crons/{id}
// Request -- all fields optional
{ "schedule": "*/5 * * * *", "enabled": false }
// Response 200
{ "id": "f47ac10b-...", "schedule": "*/5 * * * *", "enabled": false, ... }
POST /api/v1/apps/{appId}/crons/{id}/trigger
Manually fires the cron once, bypassing the schedule. Returns the enqueued job's message ID.
// Response 200
{ "msgId": 42 }
Secrets
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/apps/{appId}/secrets |
Set a secret (idempotent) |
| GET | /api/v1/apps/{appId}/secrets |
List secret key names (not values) |
| DELETE | /api/v1/apps/{appId}/secrets/{key} |
Delete a secret |
Platform Secrets
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/platform/secrets |
Set a platform-wide secret |
| GET | /api/v1/platform/secrets |
List platform secret key names |
| DELETE | /api/v1/platform/secrets/{key} |
Delete a platform secret |
Platform secrets are shared across integrations. App-level secrets are scoped to a single app.
RBAC
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/roles |
List all roles |
| POST | /api/v1/roles |
Create a role |
| PATCH | /api/v1/roles/{roleName} |
Update a role |
| DELETE | /api/v1/roles/{roleName} |
Delete a role |
| GET | /api/v1/roles/assignments |
List user-role assignments |
| POST | /api/v1/roles/assign |
Assign a role to a user |
| POST | /api/v1/roles/revoke |
Revoke a role from a user |
| GET | /api/v1/permissions |
Current user's effective permissions |
| GET | /api/v1/permissions/available |
List all available permission keys |
| GET | /api/v1/permissions/{userId} |
Specific user's permissions |
See RBAC for full request/response details.
Agents
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/agents |
List all agents |
| GET | /api/v1/apps/{appId}/agent |
Get agent details |
| PATCH | /api/v1/agents/{appId} |
Update agent name/description |
| DELETE | /api/v1/agents/{appId} |
Delete agent |
| POST | /api/v1/apps/{appId}/agent/invoke |
Invoke agent (SSE stream) |
| GET | /api/v1/apps/{appId}/agent/sessions |
List sessions (?limit=100&offset=0) |
| GET | /api/v1/apps/{appId}/agent/sessions/{sessionId} |
Get session with messages |
| GET | /api/v1/apps/{appId}/agent/sessions/{sessionId}/events |
Get session event timeline |
| GET | /api/v1/apps/{appId}/agent/approvals |
List pending approvals |
| POST | /api/v1/apps/{appId}/agent/approvals/{approvalId} |
Reply to approval |
| SSE | /api/v1/agents/stream |
Fleet-wide event stream |
POST /api/v1/apps/{appId}/agent/invoke
// Request
{ "message": "What are the open orders?", "session_id": "optional-uuid" }
Returns SSE stream. See Build an AI Agent for event types.
POST /api/v1/apps/{appId}/agent/approvals/{approvalId}
{ "action": "approve" }
// or
{ "action": "reject", "reason": "Too risky" }
Integrations
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/integrations |
List installed integrations |
| GET | /api/v1/integrations/catalog |
List available integrations |
| POST | /api/v1/integrations/catalog/{id}/deploy |
Deploy integration |
| DELETE | /api/v1/integrations/catalog/{id} |
Undeploy integration |
| PUT | /api/v1/integrations/{id}/config |
Save platform config |
| POST | /api/v1/integrations/{id}/actions/{actionId} |
Execute action |
| GET | /api/v1/integrations/{id}/auth |
Check auth status |
| POST | /api/v1/integrations/{id}/auth/start |
Start OAuth flow |
| POST | /api/v1/integrations/{id}/auth/credentials |
Store credentials |
| GET | /api/v1/integrations/auth/callback |
OAuth callback |
Channels
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/channels |
List all channels |
| POST | /api/v1/channels |
Create a channel |
| DELETE | /api/v1/channels/{channelId} |
Delete a channel |
| POST | /api/v1/channels/{channelId}/activate |
Activate and register webhook |
| POST | /api/v1/channels/{channelId}/deactivate |
Deactivate channel |
| POST | /api/v1/channels/{channelId}/link |
Create account link token (5-min TTL) |
| GET | /api/v1/channels/{channelId}/identity |
Check if user linked |
Hooks
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/apps/{appId}/hooks |
List hooks (?entity=X&operation=Y) |
| POST | /api/v1/apps/{appId}/hooks |
Create hook |
| GET | /api/v1/apps/{appId}/hooks/{hookId} |
Get hook |
| DELETE | /api/v1/apps/{appId}/hooks/{hookId} |
Delete hook |
Audit
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/audit |
Query the audit log |
GET /api/v1/audit?app_id=crm&entity=contacts&limit=100
[
{
"id": 1001,
"table_schema": "crm",
"table_name": "contacts",
"record_id": "f47ac10b-...",
"operation": "UPDATE",
"old_record": { "email": "alice@old.com" },
"new_record": { "email": "alice@acme.com" },
"changed_at": "2024-01-15T10:30:00Z"
}
]
Error format
All errors return a JSON body with an error field and the appropriate HTTP status code:
{ "error": "human-readable error message" }
| Status | Meaning | Common causes |
|---|---|---|
| 400 | Bad Request | Invalid JSON, missing required fields, validation error |
| 401 | Unauthorized | Missing or invalid Bearer token |
| 403 | Forbidden | Valid token but RBAC policy denies the action |
| 404 | Not Found | Record or entity doesn't exist |
| 500 | Internal Server Error | Unexpected database or runtime error |
| 503 | Service Unavailable | PostgreSQL or daemon not ready yet |