ROCI provides a JSON-based REST API for programmatic access to all functionality. All endpoints are mounted under /api/v1/.
The API uses session-based authentication. Authenticate through the Web UI to obtain a session cookie, then include it in API requests.
{
"success": true,
"message": "Operation completed"
}{
"error": {
"code": "not_found",
"message": "Agent not found"
}
}| Code | HTTP Status | Description |
|---|---|---|
bad_request |
400 | Invalid request parameters |
validation_error |
400 | Request validation failed |
not_found |
404 | Resource not found |
conflict |
409 | Resource conflict (e.g., duplicate name) |
internal_error |
500 | Internal server error |
Get server health status including Docker connectivity and agent count.
Response
{
"status": "ok",
"version": "0.1.0",
"uptime_secs": 3600,
"agents": 5,
"docker": true
}| Field | Type | Description |
|---|---|---|
status |
string | "ok" or "degraded" |
version |
string | Server version |
uptime_secs |
number | Seconds since server start |
agents |
number | Connected agent count |
docker |
boolean | Docker daemon reachable |
Lightweight health check (no Docker ping).
Response
{
"status": "ok",
"version": "0.1.0"
}List all registered agents.
Response
{
"agents": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"hostname": "docker-host-1",
"ip_address": "192.168.1.100",
"status": "approved",
"volume_base": "/data",
"last_seen": "2024-01-15T10:30:00Z",
"created_at": "2024-01-01T00:00:00Z",
"connected": true
}
],
"total": 1
}| Field | Type | Description |
|---|---|---|
id |
UUID | Agent unique identifier |
hostname |
string | Agent's hostname |
ip_address |
string | Agent's IP address |
status |
string | pending, approved, or rejected |
volume_base |
string | Configured volume base path |
last_seen |
string | Last heartbeat timestamp (ISO 8601) |
created_at |
string | Registration timestamp (ISO 8601) |
connected |
boolean | Currently connected via WebSocket |
List agents awaiting approval.
Response
Same format as GET /api/v1/agents, filtered to pending status.
Get a specific agent by ID.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Agent ID |
Response
Single agent object (same fields as list response).
Approve a pending agent.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Agent ID |
Response
{
"success": true,
"message": "Agent approved"
}Errors
404- Agent not found409- Agent is not pending
Reject a pending agent.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Agent ID |
Request Body (optional)
{
"reason": "Not authorized"
}Response
{
"success": true,
"message": "Agent rejected"
}Delete an agent from the system.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Agent ID |
Response
{
"success": true,
"message": "Agent deleted"
}List all applications.
Response
{
"applications": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "my-app",
"description": "My application",
"status": "running",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"total": 1
}| Field | Type | Description |
|---|---|---|
id |
UUID | Application unique identifier |
name |
string | Unique application name |
description |
string | Optional description |
status |
string | stopped, deploying, running, failed |
agent_id |
UUID | Assigned agent (null if unassigned) |
created_at |
string | Creation timestamp (ISO 8601) |
updated_at |
string | Last update timestamp (ISO 8601) |
Get a specific application by ID.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Response
Single application object.
Create a new application.
Request Body
{
"name": "my-app",
"description": "My application description",
"compose_yaml": "version: '3.8'\nservices:\n web:\n image: nginx:latest\n ports:\n - '80:80'",
"agent_id": "550e8400-e29b-41d4-a716-446655440000"
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique application name |
description |
string | No | Application description |
compose_yaml |
string | Yes | Docker Compose YAML content |
agent_id |
UUID | No | Agent to deploy to (can be assigned later) |
Response
Returns the created application (HTTP 201).
Errors
400- Validation error (empty name or compose YAML)409- Application name already exists
Update an application.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Request Body
{
"description": "Updated description",
"compose_yaml": "version: '3.8'\nservices:\n web:\n image: nginx:alpine"
}| Field | Type | Description |
|---|---|---|
description |
string | New description |
compose_yaml |
string | New compose YAML |
Note: Cannot update compose_yaml while application is running. Stop it first.
Response
Returns the updated application.
Deploy an application to an agent.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Request Body
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"secrets": [
{
"name": "DB_PASSWORD",
"value": "supersecret"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
agent_id |
UUID | No | Target agent (uses assigned agent if not specified) |
secrets |
array | No | Environment secrets to inject |
Response (HTTP 202 Accepted)
{
"success": true,
"message": "Deployment initiated"
}Errors
400- No agent specified and application has no assigned agent400- Target agent is not connected
Stop a running application.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Response
{
"success": true,
"message": "Stop command sent"
}Start a stopped application.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Response
{
"success": true,
"message": "Start command sent"
}Delete an application.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Application ID |
Request Body
{
"confirm_name": "my-app"
}| Field | Type | Required | Description |
|---|---|---|---|
confirm_name |
string | Yes | Must match the application name |
Response
{
"success": true,
"message": "Application deleted"
}List all policies ordered by priority.
Response
{
"policies": [
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Allow internal network",
"priority": 10,
"match_type": {
"type": "cidr",
"network": "192.168.0.0/16"
},
"action": "approve",
"enabled": true,
"created_at": "2024-01-01T00:00:00Z"
}
],
"total": 1
}Get a specific policy by ID.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Policy ID |
Response
Single policy object.
Create a new policy.
Request Body
{
"name": "Allow internal network",
"priority": 10,
"match_type": {
"type": "cidr",
"network": "192.168.0.0/16"
},
"action": "approve",
"enabled": true
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Policy name |
priority |
integer | Yes | — | Evaluation priority (lower = first) |
match_type |
object | Yes | — | Match condition (see below) |
action |
string | Yes | — | approve, reject, or pending |
enabled |
boolean | No | true |
Whether policy is active |
Match Type Objects
CIDR:
{"type": "cidr", "network": "192.168.0.0/16"}Reverse DNS:
{"type": "reverse_dns", "pattern": "*.internal.corp.com"}TOTP:
{"type": "totp"}Time Window:
{"type": "time_window", "seconds": 86400}Any (catch-all):
{"type": "any"}Response
Returns the created policy (HTTP 201).
Update a policy's priority.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Policy ID |
Request Body
{
"priority": 5
}Response
{
"success": true,
"message": "Priority updated"
}Enable a policy.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Policy ID |
Response
{
"success": true,
"message": "Policy enabled"
}Disable a policy.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Policy ID |
Response
{
"success": true,
"message": "Policy disabled"
}Delete a policy.
Parameters
| Name | Type | Location | Description |
|---|---|---|---|
id |
UUID | path | Policy ID |
Response
{
"success": true,
"message": "Policy deleted"
}Reload policies from database into the evaluator.
Response
{
"success": true,
"message": "Policies reloaded"
}Agents connect to the server via WebSocket at /ws/agent.
Registration
{
"type": "register",
"hostname": "docker-host-1",
"ip_address": "192.168.1.100",
"volume_base": "/data",
"totp": "123456"
}Heartbeat
{
"type": "heartbeat",
"containers": [
{
"id": "abc123",
"name": "my-app_web_1",
"image": "nginx:latest",
"status": "running"
}
]
}Command Result
{
"type": "result",
"command_id": "550e8400-e29b-41d4-a716-446655440003",
"success": true,
"output": "Container started"
}Registration Response
{
"type": "registration_response",
"status": "approved",
"agent_id": "550e8400-e29b-41d4-a716-446655440000"
}Deploy Command
{
"type": "deploy",
"app_id": "my-app",
"compose_yml": "version: '3.8'\nservices: ...",
"secrets": [
{"name": "DB_PASSWORD", "value": "secret"}
]
}Stop Command
{
"type": "stop",
"app_id": "my-app"
}Start Command
{
"type": "start",
"app_id": "my-app"
}Delete Command
{
"type": "delete",
"app_id": "my-app"
}curl -s http://localhost:8080/api/v1/agents | jqcurl -X POST http://localhost:8080/api/v1/applications \
-H "Content-Type: application/json" \
-d '{
"name": "nginx-demo",
"description": "Demo nginx application",
"compose_yaml": "version: '\''3.8'\''\nservices:\n web:\n image: nginx:alpine\n ports:\n - '\''8081:80'\''"
}'curl -X POST http://localhost:8080/api/v1/applications/{id}/deploy \
-H "Content-Type: application/json" \
-d '{
"agent_id": "550e8400-e29b-41d4-a716-446655440000"
}'curl -X POST http://localhost:8080/api/v1/policies \
-H "Content-Type: application/json" \
-d '{
"name": "Allow local network",
"priority": 10,
"match_type": {"type": "cidr", "network": "192.168.0.0/16"},
"action": "approve"
}'