clauditorium
v1.4.1
Published
REST API wrapper for the Claude CLI
Downloads
28
Maintainers
Readme
Claude API Server
A REST API wrapper for the Claude CLI.
TL;DR - Get Running in 30 Seconds
Prerequisites: Node.js 18+ and Claude CLI authenticated (claude login)
npx clauditoriumOr install globally:
npm install -g clauditorium
clauditoriumTest it:
curl -X POST http://localhost:5051/ask \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello!"}'That's it. Server runs on http://localhost:5051.
Documentation Site
Tutorial-first docs are published on GitHub Pages:
- https://erikmaday.github.io/clauditorium/
The site includes:
- Quickstart and hands-on tutorials
- Endpoint and config reference pages
- Embedded OpenAPI explorer sourced from
openapi.yaml
Features
- Simple REST API for Claude
- Per-request model selection via
modelparameter - Multi-turn chat with server-managed
conversation_idcontext - Configurable via environment variables
- Request tracking with unique IDs
- Structured JSON logs for operational observability
- Prometheus
/metricsendpoint for runtime and HTTP telemetry - Environment and request validation with consistent error responses
- Lean runtime dependencies (Express, cors, pino, prom-client)
Architecture
The server is organized into focused modules:
src/config- Environment parsing and runtime version loadingsrc/core- Logging and error helperssrc/clients- Claude CLI process wrappersrc/services- Prompt formatting helperssrc/routes- Endpoint handlers and request validationsrc/middleware- Request ID, error handling, not-found handlingsrc/app.ts- Express app compositionsrc/server.ts- Startup and bannersrc/index.ts- CLI entrypoint
Installation
# Run directly with npx (no install needed)
npx clauditorium
# Or install globally
npm install -g clauditorium
# Or install locally in a project
npm install clauditoriumDevelopment
npm run lint
npm run test
npm run test:coverage
npm run build
npm run docs:checkCoverage thresholds are enforced in CI via npm run test:coverage.
Run docs locally:
npm run docs:dev
npm run docs:build
npm run docs:previewCompatibility
- Node.js:
>=18(CI-tested on Node22) - Docker: tested via GitHub Actions Ubuntu runner with Docker Engine
- Claude auth for containerized usage: set
CLAUDE_CODE_OAUTH_TOKENat runtime
Use In Another Dockerized App
Install clauditorium in your own Node service image and run it with a Claude token:
FROM node:22-bookworm-slim
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates binutils \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g @anthropic-ai/claude-code
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["npx", "clauditorium"]Runtime example:
docker run --rm -p 5051:5051 \
-e CLAUDE_CODE_OAUTH_TOKEN="<token>" \
your-image-nameQuick smoke check (container already running on :5051):
curl -s http://localhost:5051/health
curl -s http://localhost:5051/models
curl -s -X POST http://localhost:5051/ask \
-H "Content-Type: application/json" \
-d '{"prompt":"Reply with only: smoke-ok"}'One-command smoke test script (expects image exposing port 5051):
./scripts/smoke-container.sh your-image-nameAPI Contract Governance
OpenAPI contract correctness is CI-gated with strict enforcement:
npm run openapi:types:checkverifies generated TS types fromopenapi.yamlare up to datenpm run openapi:lintvalidates spec quality and structurenpm run openapi:validateperforms full OpenAPI schema validationnpm run contract:testverifies runtime routes and response payloads againstopenapi.yaml
Run all contract checks locally:
npm run api:contract:checkAny endpoint, request/response shape, status code, or auth requirement change must include matching updates to openapi.yaml and contract tests in the same PR.
OpenAPI is the source of truth. Regenerate TypeScript contract types after spec changes with:
npm run openapi:types:generateDocs OpenAPI embed is synced from root openapi.yaml:
npm run docs:sync-openapi
npm run docs:sync-openapi:checkAPI Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| /ask | POST | Send a prompt, get a response |
| /chat | POST | Chat with persistent conversation_id context |
| /chat/:conversation_id | GET | Get conversation lifecycle metadata |
| /chat/:conversation_id | DELETE | Delete conversation from in-memory store |
| /models | GET | List available Claude models discovered from local CLI binary |
| /health | GET | Health check |
| /health/history | GET | Recent Claude CLI readiness check history |
| /health/recheck | POST | Re-run Claude CLI readiness check |
| /metrics | GET | Prometheus metrics endpoint (operational, not in OpenAPI spec) |
| /version | GET | Version info |
| /openapi.yaml | GET | Raw OpenAPI specification |
| /docs | GET | Interactive API documentation UI |
/version reports the package version from package.json at runtime.
OpenAPI spec: openapi.yaml
Interactive docs: http://localhost:5051/docs
POST /ask
curl -X POST http://localhost:5051/ask \
-H "Content-Type: application/json" \
-d '{"prompt": "What is the capital of France?"}'With a specific model:
curl -X POST http://localhost:5051/ask \
-H "Content-Type: application/json" \
-d '{"prompt": "What is the capital of France?", "model": "claude-haiku-4-5-20251001"}'POST /chat
Start a new conversation (response returns conversation_id):
curl -X POST http://localhost:5051/chat \
-H "Content-Type: application/json" \
-d '{
"message": "Hello! What is 2+2?",
"system": "You are a helpful assistant.",
"model": "claude-sonnet-4-5-20250929"
}'Continue the same chat without resending full history:
curl -X POST http://localhost:5051/chat \
-H "Content-Type: application/json" \
-d '{
"conversation_id": "YOUR_CONVERSATION_ID",
"message": "Can you summarize that?"
}'If conversation_id is unknown/expired, /chat returns 400 validation_error.
/chat responses include lifecycle and context metadata (conversation, context) on every successful call.
DELETE /chat/:conversation_id
curl -X DELETE http://localhost:5051/chat/YOUR_CONVERSATION_IDGET /chat/:conversation_id
curl http://localhost:5051/chat/YOUR_CONVERSATION_IDGET /models
curl http://localhost:5051/modelsExample response:
{
"count": 2,
"models": [
"claude-haiku-4-5-20251001",
"claude-sonnet-4-5-20250929"
]
}GET /health
curl http://localhost:5051/healthGET /health/history
curl http://localhost:5051/health/history
curl "http://localhost:5051/health/history?since=2026-01-01T00:00:00.000Z"POST /health/recheck
curl -X POST http://localhost:5051/health/recheck \
-H "x-api-key: your-api-key"GET /version
curl http://localhost:5051/versionDocumentation endpoints
curl http://localhost:5051/openapi.yamlOpen in browser: http://localhost:5051/docs
GET /metrics
curl http://localhost:5051/metricsThe model parameter is optional for both endpoints. When omitted, the CLI default model is used.
/chat responses include conversation_id, which you can reuse for continuation calls.
Using the same conversation_id intentionally carries prior message context; omit it (or delete it first) to start fresh.
If CLAUDE_API_KEY is set, requests to /ask and /chat must include:
-H "x-api-key: your-api-key"/health now includes readiness details for Claude CLI. With CLAUDE_API_STRICT_HEALTH=true, /health returns 503 when Claude CLI is not ready.
/health observability also includes Claude runtime queue metrics (active_requests, queued_requests, rejected_total, queue_timeouts_total).
GET /health/history returns an in-memory rolling window of recent readiness checks for diagnostics.
Use GET /health/history?since=2026-01-01T00:00:00.000Z to filter entries by timestamp.
POST /health/recheck triggers a new Claude CLI readiness check without restarting the server. This endpoint requires x-api-key and returns 503 if CLAUDE_API_KEY is not configured.
Error Taxonomy
| Error Code | Typical HTTP Status | Retryable? | Client Action |
|------------|---------------------|------------|---------------|
| validation_error | 400 | No | Fix request body or env value |
| unauthorized | 401 | No | Provide correct x-api-key |
| not_found | 404 | No | Correct endpoint path/method |
| payload_too_large | 413 | No | Reduce request payload size |
| rate_limited | 429 | Yes (after delay) | Wait for window to reset; retry after retry_after_seconds |
| concurrency_limited | 429 | Yes | Claude worker queue is full; retry shortly |
| queue_timeout | 504 | Yes | Queue wait exceeded configured timeout; retry |
| timeout | 504 | Yes | Retry with simpler prompt or higher timeout |
| cli_error | 500 | Sometimes | Check Claude CLI stderr/auth/session |
| spawn_error | 500 | Sometimes | Verify Claude CLI install and PATH |
| models_unavailable | 500 | Sometimes | Ensure Claude CLI is installed and locally discoverable |
| unknown_error | 500 | Yes | Retry; inspect logs if persistent |
| internal_error | 500 | Yes | Retry; inspect server logs |
| api_key_not_configured | 503 | No | Set CLAUDE_API_KEY on server |
Sample error response:
{
"error": "validation_error",
"message": "prompt is required",
"request_id": "a1b2c3d4"
}Configuration
Set these environment variables to customize behavior:
| Variable | Default | Description |
|----------|---------|-------------|
| CLAUDE_API_HOST | 127.0.0.1 | Server host |
| CLAUDE_API_PORT | 5051 | Server port |
| CLAUDE_API_TIMEOUT | 120 | Request timeout (seconds) |
| CLAUDE_API_STARTUP_CHECK_TIMEOUT | 5 | Startup timeout for Claude CLI readiness check (seconds) |
| CLAUDE_API_MAX_CONCURRENT | 4 | Max concurrent Claude subprocesses |
| CLAUDE_API_MAX_QUEUE | 100 | Max queued Claude requests waiting for a slot |
| CLAUDE_API_QUEUE_TIMEOUT_MS | 15000 | Max queue wait time before request fails |
| CLAUDE_API_DRAIN_TIMEOUT_SECONDS | 30 | Graceful shutdown drain timeout for in-flight Claude jobs (seconds) |
| CLAUDE_API_RATE_LIMIT_WINDOW_SECONDS | 60 | Per-IP rate-limit window for /ask and /chat (seconds) |
| CLAUDE_API_RATE_LIMIT_MAX_REQUESTS | 0 | Max requests per IP per window (0 disables rate limiting) |
| CLAUDE_API_HEALTH_HISTORY_LIMIT | 25 | Max readiness entries retained in memory |
| CLAUDE_API_CONVERSATION_TTL_SECONDS | 86400 | Conversation inactivity TTL (seconds) |
| CLAUDE_API_MAX_CONVERSATIONS | 1000 | Max stored conversations in memory before oldest inactive eviction |
| CLAUDE_API_CONTEXT_WARN_TOKENS | 12000 | Soft warning threshold for estimated conversation token usage |
| CLAUDE_API_CONTEXT_TARGET_TOKENS | 18000 | Soft target threshold for estimated conversation token usage |
| CLAUDE_API_CONTEXT_COMPACT_KEEP_MESSAGES | 6 | Keep this many recent messages verbatim during auto-compaction |
| CLAUDE_API_CONTEXT_SUMMARY_MAX_CHARS | 2000 | Max characters used for generated rolling summary during compaction |
| CLAUDE_API_ISOLATE_CWD | true | Run Claude in an isolated empty working directory to avoid project-context leakage |
| CLAUDE_API_CLAUDE_CWD | system temp dir | Optional override path for isolated Claude working directory |
| CLAUDE_API_BODY_LIMIT | 1mb | Max JSON request body size |
| CLAUDE_API_CORS | false | Enable CORS |
| CLAUDE_API_LOG_LEVEL | INFO | Log level |
| CLAUDE_API_STRICT_HEALTH | false | Return 503 from /health when Claude CLI is not ready |
| CLAUDE_API_KEY | (unset) | Optional API key for /ask and /chat |
Example:
CLAUDE_API_PORT=8080 clauditoriumTroubleshooting
"Claude CLI not found"
# Verify Claude CLI is installed
which claude
claude --version"Authentication required"
claude loginRequest timeouts
CLAUDE_API_TIMEOUT=300 clauditoriumCORS errors in browser
CLAUDE_API_CORS=true clauditoriumLicense
MIT - see LICENSE
