A CloudFlare Worker that proxies multiple API formats to A4F's API gateway, enabling tools like Roo Code, Cline, and other AI clients to work seamlessly with A4F's backend.
- ✅ POST /v1/messages - Main Anthropic Messages API endpoint
- ✅ POST /v1/messages/count_tokens - Token counting endpoint
- ✅ Full streaming support with proper SSE event conversion
- ✅ Handles system prompts, multi-modal content (images), and tool calling
- ✅ Near accurate Claude token counting via
@lenml/tokenizer-claude
- ✅ POST /v1/chat/completions - OpenAI Chat Completions API (pass-through)
- ✅ Full streaming support (SSE pass-through)
- ✅ Direct forwarding to A4F backend
- ✅ POST /v1/responses - OpenAI Responses API for GPT-5.1 Codex models
- ✅ Streaming and non-streaming support
- ✅ Automatic model prefix handling
- ✅ GET /v1/models - List available models (Claude + GPT Codex only)
- ✅ GET /health - Health check endpoint
- ✅ Dual authentication:
x-api-keyheader (Anthropic) orAuthorization: Bearer(OpenAI) - ✅ CORS support for browser-based clients
- ✅ Path normalization (handles
/v1/v1/and//prefixes) - ✅ Flexible path matching (supports both
/v1/*and/*paths)
All endpoints (except /health) require authentication via one of:
x-api-key: YOUR_API_KEYheader (Anthropic style)Authorization: Bearer YOUR_API_KEYheader (OpenAI style)
Different endpoints handle model names differently:
| Endpoint | Client Sends | Proxy Adds Prefix | Example |
|---|---|---|---|
/v1/messages |
Model name only | provider-7/ |
claude-sonnet-4-20250514 → provider-7/claude-sonnet-4-20250514 |
/v1/chat/completions |
Full model ID | None (pass-through) | provider-7/claude-sonnet-4-20250514 |
/v1/responses |
Model name only | provider-5/ |
gpt-5.1-codex → provider-5/gpt-5.1-codex |
/v1/models |
N/A | Strips prefixes | Returns claude-sonnet-4-20250514, gpt-5.1-codex |
curl http://localhost:8787/healthResponse:
{"status":"ok","service":"devkit-anthropic-proxy"}Returns available Claude models (from provider-7) and GPT Codex models (from provider-5) with prefixes stripped.
Note: Only GPT Codex models are included (not all GPT models) because they support the /v1/responses API. Non-codex GPT models like gpt-4o only support /v1/chat/completions and should be accessed directly via that endpoint.
curl http://localhost:8787/v1/models \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"object": "list",
"data": [
{"id": "claude-sonnet-4-20250514", "object": "model", "owned_by": "anthropic"},
{"id": "claude-opus-4-5-20251101", "object": "model", "owned_by": "anthropic"},
{"id": "gpt-5.1-codex", "object": "model", "owned_by": "openai"},
{"id": "gpt-5.1-codex-mini", "object": "model", "owned_by": "openai"}
]
}Converts Anthropic API format to OpenAI format, forwards to A4F, and converts the response back.
Non-Streaming:
curl -X POST http://localhost:8787/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'Streaming:
curl -X POST http://localhost:8787/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 100,
"stream": true,
"messages": [{"role": "user", "content": "Hello!"}]
}'Pass-through endpoint for OpenAI-format requests. Model names are NOT modified - you must include the full provider prefix.
Non-Streaming:
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "provider-7/claude-sonnet-4-20250514",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello!"}]
}'Streaming:
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "provider-7/claude-sonnet-4-20250514",
"max_tokens": 100,
"stream": true,
"messages": [{"role": "user", "content": "Hello!"}]
}'For GPT Codex models. Automatically adds provider-5/ prefix to model names.
Non-Streaming:
curl -X POST http://localhost:8787/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "gpt-5.1-codex",
"input": "Say hello"
}'Streaming:
curl -X POST http://localhost:8787/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "gpt-5.1-codex",
"input": "Say hello",
"stream": true
}'curl -X POST http://localhost:8787/v1/messages/count_tokens \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Hello, world!"}]
}'Response:
{"input_tokens": 4}The following constants are defined in src/index.ts:
| Constant | Value | Description |
|---|---|---|
A4F_BASE_URL |
https://api.a4f.co/v1 |
A4F API base URL |
A4F_PROVIDER_PREFIX |
provider-7 |
Prefix for Claude models |
A4F_RESPONSES_PROVIDER_PREFIX |
provider-5 |
Prefix for GPT-5.1 Codex models |
The /v1/responses endpoint strips the reasoning.summary field from requests before forwarding to A4F. This is because A4F's streaming implementation doesn't properly support this field. See handleResponses() for details.
Token counts are calculated locally using @lenml/tokenizer-claude rather than relying on A4F's response. This ensures consistent token counting across streaming and non-streaming requests. See countTokens().
Anthropic's tool_choice.type: "any" maps to OpenAI's "required", not "any". See convertToolChoice().
The proxy handles various path prefix issues that can occur with different clients:
- Double
/v1prefix:/v1/v1/messages→/v1/messages - Double slash prefix:
//v1/messages→/v1/messages - Flexible matching: Both
/v1/messagesand/messageswork
See the main handler in src/index.ts for implementation details.
- Bun runtime installed
bun installCreate a .dev.vars file (already in .gitignore):
# .dev.vars
A4F_API_KEY=your-real-a4f-api-key
VALID_API_KEYS=test-key-1,test-key-2bun run dev
# Or: bunx wrangler devThe worker will start on http://localhost:8787.
bun run typecheckIMPORTANT: For security and cost control, you should disable the worker when not in use.
Disabling stops all traffic to the worker but preserves your configuration and secrets.
Via CLI:
bunx wrangler disableVia CloudFlare Dashboard:
- Go to CloudFlare Dashboard
- Navigate to Workers & Pages
- Click on devkit-anthropic-proxy
- Go to Settings
- Click Disable
Via CLI:
bunx wrangler enableVia CloudFlare Dashboard:
- Go to CloudFlare Dashboard
- Navigate to Workers & Pages
- Click on devkit-anthropic-proxy
- Go to Settings
- Click Enable
Note: Disabling the worker is non-destructive. All secrets, configuration, and code are preserved. You can re-enable at any time.
- CloudFlare account
- Wrangler CLI (included via
bunx)
# Login to CloudFlare (first time only)
bunx wrangler login
# Set up required secrets
bunx wrangler secret put A4F_API_KEY
# When prompted, enter your A4F API key
bunx wrangler secret put VALID_API_KEYS
# When prompted, enter comma-separated keys: key1,key2,key3
# Deploy
bun run deploybun run deploy
# Or: bunx wrangler deployVia CLI:
bunx wrangler deployments listVia Dashboard:
- Go to Workers & Pages → devkit-anthropic-proxy
- Click on Deployments tab
bunx wrangler rollbackThis worker uses a dual-key authentication system:
- A4F API Key (
A4F_API_KEY): Your real A4F API key (never exposed to users) - User API Keys (
VALID_API_KEYS): Keys you distribute to your users/clients
bunx wrangler secret put A4F_API_KEY
# When prompted, enter your A4F API keyUser keys are stored as a comma-separated list:
bunx wrangler secret put VALID_API_KEYS
# When prompted, enter: key1,key2,key3Example input:
kL5vJ2fL3oO8cH3iO4lU5eT0uX9wP7rJ,aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV
bunx wrangler secret listThis shows secret names (not values) that are currently configured.
bunx wrangler secret delete SECRET_NAMEGenerate cryptographically secure random keys:
# Using openssl (32 hex characters)
openssl rand -hex 16
# Using openssl (longer key)
openssl rand -hex 32
# Using Node.js
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# Using Python
python3 -c "import secrets; print(secrets.token_hex(16))"Stream live logs from the production worker:
bunx wrangler tailWith pretty formatting:
bunx wrangler tail --format=prettyFilter by status:
# Only errors
bunx wrangler tail --status=error
# Only successful requests
bunx wrangler tail --status=ok- Go to CloudFlare Dashboard
- Navigate to Workers & Pages → devkit-anthropic-proxy
- View the Analytics tab for:
- Request counts
- Error rates
- CPU time usage
- Geographic distribution
-
Keep the worker disabled when not in use - This prevents unauthorized access and unexpected costs
-
Rotate user keys periodically - Generate new keys and update
VALID_API_KEYSregularly -
Use strong, random keys - Always use cryptographically secure random generators
-
Monitor usage via CloudFlare Dashboard - Check for unusual patterns or unauthorized access attempts
-
Never commit secrets - The
.dev.varsfile is gitignored; never put real keys inwrangler.toml -
Limit key distribution - Only share user keys with trusted parties
To use this proxy with Roo Code v3.35.3+ or Cline, configure the Anthropic provider:
-
Open Settings → Go to Providers tab
-
Create a new Configuration Profile:
- Click the
+button next to "Configuration Profile" - Name it something like "A4F Claude Proxy"
- Click the
-
Configure the settings:
| Setting | Value |
|---|---|
| API Provider | Anthropic |
| Anthropic API Key | Your user API key (from VALID_API_KEYS) |
| Pass Anthropic API Key as Authorization header | ✅ Checked |
| Use custom base URL | ✅ Checked |
| Custom Base URL | https://your-worker-name.your-subdomain.workers.dev OR http://localhost:8787 |
| Model | claude-opus-4-5-20251101 (or any Claude model) |
| Tool Call Protocol | XML (recommended for A4F) |
-
Model Name: Use the model name WITHOUT the provider prefix:
- ✅ Correct:
claude-sonnet-4-20250514 - ❌ Wrong:
provider-7/claude-sonnet-4-20250514
The proxy automatically adds the
provider-7/prefix when forwarding to A4F. - ✅ Correct:
-
Tool Call Protocol: Set to
XMLbecause A4F doesn't support native function calling for Claude models.
Cause: No API key provided in the request.
Solution: Include either:
x-api-key: YOUR_KEYheader (Anthropic style)Authorization: Bearer YOUR_KEYheader (OpenAI style)
Cause: The provided key is not in the VALID_API_KEYS list.
Solution:
- Verify the key is correct
- Check that
VALID_API_KEYSis set:bunx wrangler secret list - Update the keys if needed:
bunx wrangler secret put VALID_API_KEYS
Cause: The A4F_API_KEY secret is not set.
Solution:
bunx wrangler secret put A4F_API_KEYCause: The worker may be disabled or the endpoint doesn't exist.
Solution:
- Enable the worker:
bunx wrangler enable - Verify the endpoint path (e.g.,
/v1/messagesnot/messages)
Cause: Client may not support SSE or connection is being buffered.
Solution:
- Ensure
stream: trueis in the request body - Check that your client supports Server-Sent Events
- Disable any response buffering in proxies/load balancers
Cause: Token counting uses @lenml/tokenizer-claude which may differ slightly from Anthropic's internal tokenizer.
Solution: This is expected behavior. The counts are accurate for billing estimation but may vary by a few tokens from Anthropic's official API.
Cause: Various issues with wrangler or CloudFlare account.
Solution:
- Ensure you're logged in:
bunx wrangler login - Check your CloudFlare account has Workers enabled
- Verify
wrangler.tomlsyntax is correct - Try:
bunx wrangler deploy --dry-runto test without deploying
User Request (Anthropic/OpenAI format)
│
▼
┌─────────────────────────────┐
│ CloudFlare Worker │
│ ┌─────────────────────┐ │
│ │ Validate User Key │ │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Route by Endpoint │ │
│ │ /v1/messages │──►│ Convert Anthropic → OpenAI
│ │ /v1/chat/completions│──►│ Pass-through
│ │ /v1/responses │──►│ Add provider-5 prefix
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Forward to A4F │ │
│ │ (with A4F_API_KEY) │ │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Convert Response │ │
│ │ (if needed) │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
│
▼
User Response (matching request format)
Models available via the /v1/models endpoint:
claude-opus-4-5-20251101claude-sonnet-4-5-20250929claude-sonnet-4-20250514claude-3-7-sonnet-20250219claude-3-5-sonnet-20241022claude-3-5-sonnet-20240620claude-3-5-haiku-20241022claude-haiku-4-5-20251001claude-3-haiku-20240307
gpt-5-codexgpt-5.1-codexgpt-5.1-codex-minigpt-5.1-codex-max
Check A4F's documentation for the full list of available models.
ISC