A production-ready REST API wrapper for both Claude Code CLI and Gemini CLI, designed for integration with automation platforms like n8n. Supports automatic fallback between CLIs for maximum reliability.
- Dual CLI Support: Use Claude Code or Gemini CLI interchangeably
- Automatic Fallback: If one CLI fails, automatically tries the other
- RESTful API with simple HTTP endpoints
- CLI Selection: Choose which CLI to use per request or use defaults
- Metadata Tracking: Know which CLI handled each request
- Basic authentication for secure access
- Rate limiting to prevent abuse
- Comprehensive request logging
- Real-time streaming responses
- Batch processing capabilities
- Security headers via Helmet.js
- Configurable CORS support
- Request timeout protection
- Docker-ready for easy deployment
Node.js (v16 or higher)
At least one CLI must be installed:
npm install -g @anthropic-ai/claude-code
claude setup-tokennpm install -g @google/gemini-cli
# Or follow installation instructions from GoogleNote: You can install both CLIs to enable automatic fallback. If only one is installed, set it as the default in your configuration.
Clone or download the repository:
git clone <your-repo>
cd claude-gemini-cli-apiInstall dependencies:
npm installConfigure environment:
cp .env.example .envEdit the .env file with your settings:
PORT=3000
AUTH_ENABLED=true
AUTH_USERS=admin:your_secure_password,n8n:another_password
MAX_PROMPT_LENGTH=100000
REQUEST_TIMEOUT=300000
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX=100
LOG_LEVEL=info
CORS_ORIGIN=*
DEFAULT_CLI=claude
ENABLE_FALLBACK=true
CLAUDE_DEFAULT_MODEL=sonnet
GEMINI_DEFAULT_MODEL=gemini-2.5-flashStart the server:
# Production
npm start
# Development (with auto-reload)
npm run devGET /health
No authentication required. Returns server status.
Response:
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00.000Z",
"uptime": 123.456
}GET /api/info
Returns API information and current configuration.
Response:
{
"version": "1.0.0",
"endpoints": {
"/api/ask": "Simple prompt execution",
"/api/process": "Advanced prompt execution with all options",
"/api/stream": "Streaming response",
"/api/batch": "Batch processing multiple prompts"
},
"config": {
"authEnabled": true,
"maxPromptLength": 100000,
"requestTimeout": 300000,
"rateLimitWindow": 900000,
"rateLimitMax": 100,
"defaultModel": "sonnet"
}
}GET /api/test?cli=claude or /api/test?cli=gemini
Verifies that the specified CLI is working correctly. If no CLI is specified, tests the default CLI.
Query Parameters:
cli(optional): CLI to test (claudeorgemini)
Response:
{
"success": true,
"message": "Claude CLI is working correctly",
"response": "Hello from Claude CLI API!",
"usedCLI": "claude"
}POST /api/ask
The recommended endpoint for most use cases. Executes a prompt with minimal configuration.
Request Body:
{
"prompt": "Summarize this text in 3 bullet points: [YOUR TEXT]",
"outputFormat": "json",
"model": "sonnet",
"systemPrompt": "You are a helpful assistant",
"cli": "claude"
}Parameters:
prompt(required): The prompt text to executeoutputFormat(optional): Output format -text,json, orstream-json(default:json)model(optional): Model to use (default:sonnetfor Claude,gemini-2.5-flashfor Gemini)systemPrompt(optional): Custom system promptcli(optional): Which CLI to use -claudeorgemini(default: from config)
Response:
{
"response": "Your answer here...",
"_meta": {
"usedCLI": "claude",
"fallbackUsed": false
}
}Notes:
- If the specified CLI fails and fallback is enabled, the API will automatically try the other CLI
- The
_metafield in the response tells you which CLI was actually used - Setting
disableFallback: truein the request body will prevent automatic fallback
POST /api/process
Advanced execution with full access to Claude Code and Gemini CLI options.
Request Body:
{
"prompt": "Analyze this data",
"outputFormat": "json",
"model": "sonnet",
"cli": "claude",
"systemPrompt": "You are a data analyst",
"appendSystemPrompt": "Always output valid JSON",
"allowedTools": ["Bash", "Edit"],
"disallowedTools": ["WebSearch"],
"dangerouslySkipPermissions": false,
"disableFallback": false,
"settings": {
"customOption": "value"
},
"mcpConfig": ["/path/to/mcp-config.json"],
"sessionId": "uuid-here",
"continueSession": false,
"resumeSession": null
}Parameters:
prompt(required): The prompt to executeoutputFormat: Output formatmodel: Model name or aliascli(optional): Which CLI to use (claudeorgemini)systemPrompt: Replace default system promptappendSystemPrompt: Add to default system prompt (Claude only)allowedTools: Array of allowed toolsdisallowedTools: Array of disallowed toolsdangerouslySkipPermissions: Skip permission checks (use with caution, maps to--yoloin Gemini)disableFallback: Set totrueto prevent automatic fallbacksettings: Additional settings object (Claude only)mcpConfig: Array of MCP config file paths (Claude only)sessionId: Specific session ID to usecontinueSession: Continue most recent conversation (Claude only)resumeSession: Resume specific session by ID
Response:
{
"success": true,
"data": {
"response": "..."
},
"metadata": {
"model": "sonnet",
"outputFormat": "json",
"timestamp": "2024-01-01T12:00:00.000Z",
"usedCLI": "claude",
"fallbackUsed": false
}
}POST /api/stream
Returns real-time streaming responses as NDJSON.
Request Body:
{
"prompt": "Write a long story",
"model": "sonnet",
"cli": "claude",
"systemPrompt": "You are a creative writer",
"includePartialMessages": true
}Parameters:
cli(optional): Which CLI to use (claudeorgemini)includePartialMessages(optional): Include partial messages in stream (Claude only, default:true)
Response:
Stream of newline-delimited JSON:
{"type":"content_block_start","content":{"type":"text","text":""}}
{"type":"content_block_delta","delta":{"type":"text_delta","text":"Once"}}
{"type":"content_block_delta","delta":{"type":"text_delta","text":" upon"}}
...
POST /api/batch
Process multiple prompts in sequence.
Request Body:
{
"prompts": [
"Summarize: [text1]",
"Analyze: [text2]",
{
"prompt": "Translate: [text3]",
"model": "opus",
"cli": "claude",
"systemPrompt": "You are a translator"
},
{
"prompt": "Calculate: 15 * 23",
"cli": "gemini"
}
],
"outputFormat": "json",
"model": "sonnet",
"cli": "claude",
"systemPrompt": "Default system prompt"
}Parameters:
- Each prompt can specify its own
cli,model, andsystemPrompt - Common options apply to all prompts that don't override them
Response:
{
"success": true,
"results": [
{
"index": 0,
"success": true,
"data": { "response": "..." },
"usedCLI": "claude",
"fallbackUsed": false
},
{
"index": 1,
"success": true,
"data": { "response": "..." },
"usedCLI": "gemini",
"fallbackUsed": true
}
],
"summary": {
"total": 3,
"successful": 2,
"failed": 1
}
}When AUTH_ENABLED=true, the API uses HTTP Basic Authentication.
Using curl:
curl -u admin:changeme123 \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello"}'Using JavaScript:
fetch("http://localhost:3000/api/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Basic " + btoa("admin:changeme123"),
},
body: JSON.stringify({
prompt: "Hello",
}),
});Using n8n:
- Add HTTP Request node
- Set Authentication to Basic Auth
- Enter username and password
- Configure request body as shown above
{
"method": "POST",
"url": "http://localhost:3000/api/ask",
"authentication": "basicAuth",
"bodyParameters": {
"prompt": "Summarize in 3 points: {{$json.text}}",
"outputFormat": "json"
}
}{
"method": "POST",
"url": "http://localhost:3000/api/ask",
"authentication": "basicAuth",
"bodyParameters": {
"prompt": "Convert this to JSON: {{$json.csvData}}",
"outputFormat": "json",
"systemPrompt": "Output only valid JSON"
}
}{
"method": "POST",
"url": "http://localhost:3000/api/batch",
"authentication": "basicAuth",
"bodyParameters": {
"prompts": [
"Analyze sentiment: {{$json.review1}}",
"Analyze sentiment: {{$json.review2}}",
"Analyze sentiment: {{$json.review3}}"
],
"outputFormat": "json"
}
}{
"method": "POST",
"url": "http://localhost:3000/api/stream",
"authentication": "basicAuth",
"bodyParameters": {
"prompt": "Write detailed report: {{$json.topic}}",
"includePartialMessages": true
}
}curl -u admin:password \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{
"prompt": "Summarize this article in 3 bullet points: [ARTICLE TEXT]",
"outputFormat": "json"
}'curl -u admin:password \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{
"prompt": "Extract all email addresses from this text: [TEXT]",
"outputFormat": "json",
"systemPrompt": "Output as JSON array"
}'curl -u admin:password \
-X POST http://localhost:3000/api/process \
-H "Content-Type: application/json" \
-d '{
"prompt": "Convert this CSV to structured JSON: [CSV]",
"outputFormat": "json",
"systemPrompt": "Output only valid JSON with proper types"
}'curl -u admin:password \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{
"prompt": "Write a Python function to calculate fibonacci",
"outputFormat": "text"
}'curl -u admin:password \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{
"prompt": "Analyze this sales data and provide insights: [DATA]",
"outputFormat": "json",
"model": "sonnet"
}'Build the Docker image:
docker build -t claude-code-api .Run the container:
docker run -d \
-p 3000:3000 \
-e AUTH_ENABLED=true \
-e AUTH_USERS=admin:password \
--name claude-api \
claude-code-apiUsing Docker Compose:
docker-compose up -d- Always enable authentication in production environments
- Use strong passwords with minimum 16 characters
- Deploy behind HTTPS using a reverse proxy (nginx or Caddy)
- Restrict CORS origins - avoid using
*in production - Set appropriate rate limits based on your usage patterns
- Monitor logs regularly for suspicious activity
- Keep dependencies updated
- Never hardcode secrets - use environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
AUTH_ENABLED |
false |
Enable authentication |
AUTH_USERS |
admin:changeme |
Comma-separated user:pass pairs |
MAX_PROMPT_LENGTH |
100000 |
Maximum prompt length in characters |
REQUEST_TIMEOUT |
300000 |
Request timeout in milliseconds (5 min) |
RATE_LIMIT_WINDOW |
900000 |
Rate limit window in ms (15 min) |
RATE_LIMIT_MAX |
100 |
Max requests per window |
LOG_LEVEL |
info |
Logging level (info/combined/none) |
CORS_ORIGIN |
* |
Allowed CORS origins |
DEFAULT_CLI |
claude |
Default CLI to use (claude or gemini) |
ENABLE_FALLBACK |
true |
Enable automatic fallback to alternate CLI |
CLAUDE_DEFAULT_MODEL |
sonnet |
Default model for Claude CLI (sonnet/opus/haiku) |
GEMINI_DEFAULT_MODEL |
gemini-2.5-flash |
Default model for Gemini CLI |
The API supports both Claude Code CLI and Gemini CLI with intelligent fallback:
- Default Behavior: Uses
DEFAULT_CLIfrom config (default:claude) - Per-Request Override: Specify
"cli": "gemini"or"cli": "claude"in request body - Automatic Fallback: If primary CLI fails and
ENABLE_FALLBACK=true, automatically tries the other CLI - Metadata Tracking: All responses include which CLI was used and whether fallback occurred
Claude Code CLI:
- Full support for all options (session management, MCP config, tool control)
--append-system-promptflag--include-partial-messagesfor streaming- Session continuation with
--continueand--resume
Gemini CLI:
- Simplified parameter mapping
- System prompts are prepended to user prompt
--yoloflag for auto-approval (maps todangerouslySkipPermissions)- Native support for Gemini models
Force specific CLI:
{
"prompt": "Explain AI",
"cli": "gemini",
"disableFallback": true
}Let API choose with fallback:
{
"prompt": "Explain AI"
// Uses DEFAULT_CLI, falls back if needed
}Check which CLI was used:
const response = await fetch("/api/ask", {
method: "POST",
body: JSON.stringify({ prompt: "Hello" }),
});
const result = await response.json();
console.log(`Used: ${result._meta.usedCLI}`);
console.log(`Fallback: ${result._meta.fallbackUsed}`);Run automated tests:
npm testManual testing steps:
Check health:
curl http://localhost:3000/healthTest Claude Code:
curl -u admin:password http://localhost:3000/api/testExecute a simple prompt:
curl -u admin:password \
-X POST http://localhost:3000/api/ask \
-H "Content-Type: application/json" \
-d '{"prompt": "Say hello"}'View logs using pm2:
pm2 logs claude-apiView logs using Docker:
docker logs -f claude-apiDirect log access:
tail -f logs/api.logThe API uses Morgan for request logging. Log format:
::1 - - [01/Jan/2024:12:00:00 +0000] "POST /api/ask HTTP/1.1" 200 123 "-" "curl/7.64.1"
Install globally:
npm install -g @anthropic-ai/claude-codeOr add to PATH:
export PATH=$PATH:/path/to/claude- Verify
.envfile has correct format - Check
AUTH_USERS=username:passwordsyntax - Test without authentication first by setting
AUTH_ENABLED=false
- Increase
REQUEST_TIMEOUTin.env - Verify Claude Code is responding
- Check network connectivity
- Increase
RATE_LIMIT_MAXvalue - Extend
RATE_LIMIT_WINDOWduration - Consider using multiple API keys
{
"prompt": "Search the web for...",
"mcpConfig": ["/path/to/mcp-config.json"],
"allowedTools": ["WebSearch"]
}{
"prompt": "Continue our discussion",
"continueSession": true
}{
"prompt": "Edit this file",
"allowedTools": ["Edit", "View"],
"disallowedTools": ["Bash"]
}Contributions are welcome. To contribute:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
MIT License - See LICENSE file for details
For issues and questions:
- Open an issue on GitHub
- Consult Claude Code documentation
- Review n8n integration guides
Built with Express.js and Claude Code CLI by Anthropic, along with various open-source packages.