The REST API runs on a separate port (default 8080) alongside the gRPC server (port 50051). Built with axum, it provides management endpoints for projects, sessions, health checks, and real-time events via SSE.
OpenAPI 3.x spec is available at runtime: GET /api/v1/openapi.json
All /api/v1/* endpoints (except /api/v1/openapi.json) require authentication.
Authorization: Bearer <token>
Rate limiting is enforced per client IP. Exceeding the limit returns:
HTTP 429 Too Many Requests
Retry-After: <seconds>
Public endpoints (no auth): /healthz, /api/v1/openapi.json
Liveness probe. No auth required.
200 OK
"ok"
Detailed health check with system metrics.
{
"version": "0.1.0",
"api_version": "v1",
"uptime_seconds": 3600,
"sessions": 5,
"docker": true,
"disk": {
"total_bytes": 500107862016,
"available_bytes": 250000000000
},
"memory": {
"total_bytes": 17179869184,
"used_bytes": 8589934592
}
}System metrics.
{
"uptime_seconds": 3600,
"active_sessions": 5,
"max_sessions": 50,
"memory": {
"total_bytes": 17179869184,
"used_bytes": 8589934592
}
}Create a project (starts git clone).
Request:
{
"name": "my-project",
"git_url": "https://github.com/user/repo.git"
}Response: 201 Created
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-project",
"git_url": "https://github.com/user/repo.git",
"default_image": null,
"state": "cloning",
"created_at": "2026-04-13T10:00:00Z",
"updated_at": "2026-04-13T10:00:00Z"
}Errors:
400— invalid URL or SSRF validation failure409— project with this git_url already exists
List all projects.
Response: 200 OK
[
{
"id": "...",
"name": "my-project",
"git_url": "https://github.com/user/repo.git",
"default_image": null,
"state": "ready",
"created_at": "...",
"updated_at": "..."
}
]Get project by ID.
Response: 200 OK — project object, or 404 Not Found
Delete a project and its data.
Response: 204 No Content, or 404 Not Found
Retry a failed clone.
Response: 200 OK — updated project object
Errors:
404— project not found409— project is not infailedstate
Create a session within a project.
Request:
{
"profile": "claude-default"
}{
**Errors:**
- `404` — project not found
- `409` — project not in `ready` state
- `429` — session limit reached for this project
#### `GET /api/v1/projects/{project_id}/sessions`
List sessions for a specific project.
#### `GET /api/v1/sessions`
List all sessions globally. Supports pagination:
- `?limit=20` — max results (default: 20)
- `?offset=0` — skip N results
#### `GET /api/v1/sessions/{id}`
Get session by ID.
**Response:** `200 OK`
```json
{
"id": "...",
"project_id": "...",
"container_id": "abc123def456",
"state": "running",
"profile": "claude-default",
"created_at": "...",
"updated_at": "...",
"stopped_at": null,
"error_reason": null
}
Stop a running session.
Response: 200 OK — updated session object
Errors: 404, 409 (not in stoppable state)
Resume a stopped session.
Response: 200 OK — updated session object
Errors: 404, 409 (not in stopped state)
Retry a failed session.
Response: 200 OK — updated session object
Errors: 404, 409 (not in failed state)
Delete a session record. Container is stopped and removed if still running.
Response: 204 No Content
List available Docker images.
Response: 200 OK
[
{
"reference": "ubuntu:24.04",
"is_default": true
}
]List configured session profiles.
Response: 200 OK
[
{
"name": "claude-default",
"init_command": null,
"env_vars": { "NODE_ENV": "production" }
}
]Server-Sent Events stream. See SSE Events.
All errors return a JSON envelope:
{
"error": "Human-readable error message"
}HTTP status mapping from RelayError:
| Error type | Status |
|---|---|
| NotFound | 404 |
| Auth | 403 |
| SessionLimit | 429 |
| Config, Git | 400 |
| Database, Docker | 500 |
All mutating requests (POST, PUT, PATCH, DELETE) are logged with:
- HTTP method
- Request path
- Source IP
- Response status code