OpenAI-compatible API gateway running on Cloudflare Workers AI. Zero dependencies. Instant access to 100+ AI models — LLMs, image generators, speech, embeddings, and more — behind a standard OpenAI API interface.
Live Demo — password: cng-public-demo
graph TD
Client["🖥️ Client\nOpenAI SDK · curl · any app"]
CNG["⚡ CNG Worker\nCloudflare Edge"]
CF["🧠 Cloudflare Workers AI\n100+ models"]
Client -- "OpenAI-compatible API" --> CNG
CNG -- "env.AI binding (primary)" --> CF
CNG -. "REST API fallback" .-> CF
| Feature | CNG | openai-cf-workers-ai |
ai-gateway-wrapper |
antonai |
CF built-in |
|---|---|---|---|---|---|
| Chat (streaming) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vision | ✅ | — | — | — | — |
| Tool calls | ✅ | — | — | — | — |
| Reasoning | ✅ | — | — | — | — |
| Image generation | ✅ | ✅ | — | — | — |
| Image editing | ✅ inpainting | — | — | — | — |
| Audio STT | ✅ | ✅ | — | ✅ | — |
| Audio TTS | ✅ | — | — | ✅ | — |
| Embeddings | ✅ | ✅ | — | — | ✅ |
| Translation | ✅ | — | — | — | — |
| Moderation | ✅ | — | — | — | — |
| Feature | CNG | openai-cf-workers-ai |
ai-gateway-wrapper |
antonai |
CF built-in |
|---|---|---|---|---|---|
| Config UI | ✅ built-in | — | — | Chat UI | — |
| Playground | ✅ chat/image/audio | — | — | — | — |
| Model discovery | ✅ live catalog | — | — | — | — |
| Model aliasing | ✅ + spoof | — | — | — | — |
| Model health | ✅ error badges | — | — | — | — |
| Rate limiting | ✅ per-user RPM | — | — | — | — |
| Dynamic users | ✅ admin/client | — | Dummy key | — | — |
| SSO | ✅ CF Access | — | — | — | — |
| Cost tracking | ✅ real-time | — | AI Gateway | — | — |
| KV persistence | ✅ optional | — | — | ✅ | — |
| REST API fallback | ✅ auto | — | — | — | — |
| Zero dependencies | ✅ | — | — | — | — |
| Single-file deploy | ✅ | — | — | — | — |
Forks the repo and deploys to your Cloudflare account. The deploy page prompts for CF_ACCOUNT_ID, CF_API_TOKEN, and API_KEY — fill them in and click Create and deploy. For API_KEY, generate a random string:
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='Save the generated key — you'll need it to access the config panel.
No clone needed — downloads the latest bundle and deploys interactively:
curl -fsSL https://raw.githubusercontent.com/noizo/cng/main/setup.sh | bashgit clone https://github.com/noizo/cng.git
cd cng
./setup.shBoth use the same script. From a clone it deploys from source; via curl it downloads the pre-built bundle. The script generates an admin key, walks you through account ID, API token, optional KV, and starter model catalog.
Prerequisites: Node.js and Wrangler
npm install -g wrangler
wrangler login# 1. Edit wrangler.toml — set worker name
# Optionally uncomment the KV section for persistent config
# 2. Deploy
wrangler deploy
# 3. Set secrets — API_KEY is the admin key (full access)
echo "<your-cloudflare-account-id>" | wrangler secret put CF_ACCOUNT_ID
echo "<your-cloudflare-api-token>" | wrangler secret put CF_API_TOKEN
echo "$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" | wrangler secret put API_KEY
# 4. (Optional) Client key — inference only, no config access
echo "$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" | wrangler secret put API_KEY_2For teams or reproducible deployments. Complete Terraform configuration with worker, KV, custom domain, and SSO is in examples/terraform/.
| Secret | Required | Description |
|---|---|---|
CF_ACCOUNT_ID |
✅ | Your Cloudflare account ID — find it on the dashboard overview (right sidebar) |
CF_API_TOKEN |
✅ | API token — needed for model discovery, analytics, and REST API fallback. See how to create one |
API_KEY |
✅ | Admin key for the gateway. Auto-generated by setup.sh, or create your own (see generating API_KEY) |
API_KEY_2 |
— | Optional client key — inference only, no config access |
GATEWAY_CONFIG |
— | JSON config string — persists settings on free tier without KV (see below) |
| Binding | Required | Description |
|---|---|---|
AI (Workers AI) |
✅ | Native Workers AI binding — primary inference path. Configured automatically via wrangler.toml |
CONFIG (KV) |
— | KV namespace for persistent config and dynamic users. Without it, config is in-memory (resets on cold start) and only env keys work |
CNG needs three required secrets to operate:
CF_ACCOUNT_ID+CF_API_TOKENare your Cloudflare credentials. Even though theAIbinding handles inference directly, the token is still required for model discovery (the "Add model" panel), usage analytics (/api/status), and as an automatic fallback if the AI binding is ever unavailable. Without these, you cannot add or manage models.API_KEYis the admin gateway key. This is not a Cloudflare token — it's a random secret that protects your CNG instance. Anyone with this key has full access to config, model management, and inference. Thesetup.shscript generates one automatically, or you can create your own.API_KEY_2is an optional client key. It can only call inference endpoints (/v1/chat/completions,/v1/images/generations, etc.) and view models. Admin routes return403 Forbidden.- KV users created through the config panel are always client role.
CF_API_TOKEN is a Cloudflare API Token that gives CNG access to model discovery, usage analytics, and the REST API fallback.
| Scope | Category | Permission | Used by |
|---|---|---|---|
| Workers AI | Account | Read + Edit | Model discovery (/api/discover), REST API inference fallback |
| Account Analytics | Account | Read | Neuron usage, cost projections, worker invocation stats (/api/status) |
| Workers KV Storage | Account | Edit | Persistent config and dynamic users (only if using KV) |
Without Workers AI permissions, the "Add model" panel and REST API fallback will not work. Without Account Analytics, the status dashboard will show zeros.
- Go to Cloudflare API Tokens
- Click Create Token → Create Custom Token
- Add the permissions listed above
- Account Resources → select your specific account (or "All accounts")
- Click Continue to summary → Create Token
- Copy the token and set it:
echo "<your-token>" | wrangler secret put CF_API_TOKENTip: Cloudflare also offers a quick template — on the Workers AI page, click "Use REST API" → "Create a Workers AI API Token". This pre-fills Workers AI Read + Edit, but you still need to add Account Analytics Read manually.
API_KEY is a random secret that protects your gateway. It's not related to Cloudflare — you generate it yourself.
Automatic (recommended) — setup.sh and install.sh generate one for you during setup.
Manual:
openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' | wrangler secret put API_KEYThis generates a secure random key and sets it as a worker secret in one command. Save the output — you'll need it to access the config panel and make API calls.
https://<worker-name>.<subdomain>.workers.dev/v1
Or your custom domain if configured.
Authorization: Bearer <your-api-key>
| Method | Path | Auth | Admin | Description |
|---|---|---|---|---|
POST |
/v1/chat/completions |
✅ | — | Chat completions (streaming supported) |
POST |
/v1/images/generations |
✅ | — | Image generation |
POST |
/v1/images/edits |
✅ | — | Image inpainting (multipart) |
POST |
/v1/embeddings |
✅ | — | Text embeddings |
POST |
/v1/audio/transcriptions |
✅ | — | Speech-to-text (multipart) |
POST |
/v1/audio/translations |
✅ | — | Audio translation (multipart) |
POST |
/v1/audio/speech |
✅ | — | Text-to-speech |
POST |
/v1/translations |
✅ | — | Text translation |
POST |
/v1/moderations |
✅ | — | Content moderation |
GET |
/v1/models |
✅ | — | List available models |
GET |
/api/status |
✅ | — | Live status + costs (JSON) |
GET |
/config |
— | — | Web config panel |
GET |
/img/{id} |
— | — | Cached generated images |
GET |
/api/config |
✅ | ✅ | Current config (JSON) |
POST |
/api/config |
✅ | ✅ | Save config |
GET |
/api/discover |
✅ | ✅ | Browse Cloudflare model catalog |
GET |
/api/users |
✅ | ✅ | API key management |
POST |
/api/users |
✅ | ✅ | User CRUD (requires KV) |
Chat:
curl https://your-worker.workers.dev/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'Image generation:
curl https://your-worker.workers.dev/v1/images/generations \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model": "dall-e-3", "prompt": "A mountain at sunset", "size": "1920x1080"}'Transcription:
curl https://your-worker.workers.dev/v1/audio/transcriptions \
-H "Authorization: Bearer $KEY" \
-F file=@audio.mp3 \
-F model=whisper-1Text-to-speech:
curl https://your-worker.workers.dev/v1/audio/speech \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model": "tts-1", "input": "Hello world"}' \
--output speech.mp3Status:
curl -H "Authorization: Bearer $KEY" https://your-worker.workers.dev/api/status | jqCNG uses role-based access control with two roles:
| Role | Source | Can do |
|---|---|---|
admin |
API_KEY env secret |
All inference + config management + user CRUD |
client |
API_KEY_2 env secret, KV-created users |
Inference + model list + status only |
Admin routes (/api/config, /api/discover, /api/users) return 403 Forbidden for client keys.
Dynamic user management (create/delete via /api/users) requires the KV CONFIG binding. Without KV, only the two env keys are available.
CNG does not hardcode a fixed model list. Models are managed entirely through the config panel — add, remove, and reorder any model from the Cloudflare Workers AI catalog.
- Open the config panel at
/config - Click + Add under any category (Chat, Image, Voice, Utility)
- A discovery modal queries the Cloudflare Workers AI catalog in real-time
- Browse, search, and toggle models on/off — complete with capability badges, pricing, and context window info
- Changes auto-save (with KV) or can be exported as JSON
The gateway ships with a sensible default set, but you can replace it entirely with whatever models Cloudflare offers — no code changes, no redeployment.
| Category | Endpoint | Examples |
|---|---|---|
| Chat | /v1/chat/completions |
Qwen, GLM, Llama, GPT-OSS, Kimi, DeepSeek |
| Image | /v1/images/generations |
Flux 1/2, SDXL Lightning, Leonardo Phoenix, Dreamshaper |
| Voice | /v1/audio/* |
Whisper (STT), Deepgram Aura (TTS), MeloTTS |
| Utility | /v1/embeddings, /v1/moderations, /v1/translations |
BGE (embeddings), Llama Guard (moderation), M2M100 (translation) |
Aliases map familiar names (like gpt-4o) to real Cloudflare models. Aliases always resolve for every API key — if a client sends "model": "gpt-4o", the gateway routes it to the mapped backend regardless of spoof mode.
Spoof mode controls what /v1/models returns for a given API key:
| Spoof aliases | /v1/models returns |
Use case |
|---|---|---|
| OFF (default) | Real Cloudflare model IDs | Direct usage, development, transparency |
| ON | Only alias names — real models hidden | Drop-in OpenAI replacement for clients that expect OpenAI model names |
Many OpenAI-compatible clients (chat UIs, plugins, automation tools) query /v1/models to populate their model selector. If they see unfamiliar names like qwen3-30b-a3b-fp8, they either:
- Don't display them (hard-coded OpenAI model lists)
- Show confusing names to end users
- Fail validation checks
With spoof ON, the client sees gpt-4o, dall-e-3, whisper-1 — names it expects. The gateway transparently routes these to the configured Cloudflare backends.
Spoof is toggled per API key in the config panel. This lets you run mixed setups:
- Key A (spoof ON) — used by a chat UI that expects OpenAI names
- Key B (spoof OFF) — used by scripts or direct API calls that use real model IDs
Both keys can use aliases in their requests regardless of the spoof setting.
Access at https://your-worker/config — protected by SSO (see below) or ?key=<your-api-key>.
Your API key is stored in localStorage and persists across browser sessions. To log out, click the browser DevTools console and run localStorage.removeItem("cng_key"), or clear site data.
The panel is served directly by the worker — no external dependencies. It provides:
- Models — four columns (Chat, Image, Voice, Utility) with scrollable lists, reorder, and per-category discovery from the Cloudflare catalog
- Config — API keys with inline renaming, per-key spoof toggle, and aliases with target model selectors
- Live Status — real-time neuron usage, cost projections, auto-refresh
- API Reference — inline endpoint docs and spoof mode explanation
- Export JSON — download current config for backup or wrangler import
| Feature | Free tier (no KV) | Paid ($5/mo with KV) |
|---|---|---|
| Gateway routing | ✅ | ✅ |
| Config panel | ✅ | ✅ |
| Config changes | Via GATEWAY_CONFIG secret |
Persistent in KV |
| Dynamic users | Via JSON export/import (see below) | Create/delete via panel |
| Export JSON | ✅ | ✅ |
Without KV, config lives in memory and resets on cold start. To persist your settings:
- Configure everything in the config panel
- Click Export JSON to download your config
- Save it as a worker secret:
wrangler secret put GATEWAY_CONFIG < cng-config.jsonThe worker loads GATEWAY_CONFIG on startup and merges it with defaults. Repeat these steps whenever you change settings.
KV users don't need this — their config persists automatically. Export JSON is just a backup for KV users.
Without KV, you can't create users from the config panel. Instead, generate a key, hash it, and add the user to your exported config JSON:
# 1. Generate a key for the new user
KEY=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
echo "New user key: $KEY"
# 2. Compute the SHA-256 hash
HASH=$(printf '%s' "$KEY" | shasum -a 256 | cut -d' ' -f1)
# 3. Add to your config JSON (cng-config.json)
# Insert into the "users" array:
# { "id": "bob", "keyHash": "<hash>", "role": "client", "created": "2026-03-31" }Your cng-config.json should have a users array:
{
"users": [
{ "id": "bob", "keyHash": "a1b2c3...", "role": "client", "created": "2026-03-31" }
],
"chatModels": [ ... ]
}Then apply it:
wrangler secret put GATEWAY_CONFIG < cng-config.jsonThe user can now authenticate with Authorization: Bearer <KEY>. Repeat for additional users — each needs a unique id and keyHash.
Optionally protect the config panel with GitHub SSO via Cloudflare Zero Trust Access. This adds a login gate before the panel — no code changes to the worker.
-
Create a GitHub OAuth App at github.com/settings/developers:
- Homepage URL:
https://your-worker-domain/config - Authorization callback URL:
https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback - Note the Client ID and Client Secret
- Homepage URL:
-
Find your team name — this is the subdomain you chose when setting up Cloudflare Zero Trust (visible at Zero Trust → Settings → Custom Pages). If you haven't enabled Zero Trust yet, go to the Zero Trust dashboard and complete the onboarding — it will ask you to pick a team name.
- Go to Zero Trust → Settings → Authentication → Login methods
- Click Add new → GitHub
- Enter the Client ID and Client Secret from step 1
- Save
Then create the access application:
- Go to Zero Trust → Access → Applications
- Click Add an application → Self-hosted
- Set the Application domain to
your-worker-domainand Path to/config - Set Session Duration to 24 hours
- Under Identity providers, select the GitHub provider you just added
- Enable Auto-redirect to identity (skip the provider picker when there's only one)
- Under Policies, add a policy:
- Name: Allow admin
- Action: Allow
- Include: Emails — enter your GitHub email(s)
- Save
Visiting /config now redirects to GitHub login. After auth, Cloudflare sets a 24h session cookie.
The same SSO configuration is available as Terraform in examples/terraform/sso.tf — set enable_sso = true and provide your OAuth credentials.
| Path | Auth |
|---|---|
/config |
SSO (GitHub login via Cloudflare Access) |
/v1/*, /api/* |
Bearer token (unchanged) |
/img/* |
Unauthenticated (ephemeral UUIDs, 1h cache) |
The panel still uses your API key internally for /api/* calls — SSO handles who can access the page, the Bearer token handles what the page can do.
Works with any OpenAI-compatible client:
from openai import OpenAI
client = OpenAI(
base_url="https://your-worker.workers.dev/v1",
api_key="your-api-key",
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://your-worker.workers.dev/v1',
apiKey: 'your-api-key',
});
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello!' }],
});With spoof OFF, use real model IDs instead of aliases.
Cloudflare Workers AI uses neurons as the billing unit.
| Tier | Included | Overage |
|---|---|---|
| Free | 10,000 neurons/day | — |
| Paid ($5/mo) | 10,000 neurons/day | $0.011 per 1,000 neurons |
The /api/status endpoint and config panel show real-time neuron usage and cost projections.
The admin "password" is the API_KEY secret bound to the worker. To reset it:
openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' | wrangler secret put API_KEYAfter resetting, clear the old key from your browser — open DevTools console and run localStorage.removeItem("cng_key"), then visit /config and enter the new key.
npm test # Run all tests (node:test, zero deps)
npm run bundle # Build single-file bundle → dist/cng.js
npm run deploy:dry # Dry-run deploy (verify bundle)
npm run deploy # Deploy to CloudflareThis project was created for personal use. The author assumes no responsibility for any financial loss, unexpected charges, or damages arising from the use of this software. Use at your own risk.
All contributions are welcome — bug reports, feature requests, pull requests. Feel free to open an issue or submit a PR.
| Project | Stars | License |
|---|---|---|
openai-cf-workers-ai |
279 | MIT |
ai-gateway-openai-wrapper |
143 | AGPL-3.0 |
antonai |
110 | MIT |
| CF built-in | — | — |
Apache-2.0