If you're building a side project and just want an LLM endpoint without wiring up a paid API key, Cloudflare Workers AI is worth a look. The free plan includes a daily allocation of "neurons" (Cloudflare's usage unit) for open models like Llama 3.1 8B and m2m100. That's plenty for prototypes, demos, and low-traffic tools. Limits are set by Cloudflare and can change, so check the current pricing page.
I packaged two small, tested Workers as an MIT-licensed free download: cf-ai-starter-lite.zip. The full code is below too, so you can copy it straight from this post.
01-chat-api: a chat endpoint with conversation history, a configurable system prompt, CORS, and optional bearer-token auth04-translate: a translation endpoint for 100 languages (m2m100), callable with GET or POST
01-chat-api/wrangler.toml:
name = "cf-chat-api"
main = "worker.js"
compatibility_date = "2026-10-01"
[ai]
binding = "AI"
The [ai] binding is the only thing that makes Workers AI available as env.AI.
01-chat-api/worker.js:
// Free LLM chat API on Cloudflare Workers AI
// POST /chat {"message":"hi","history":[{"role":"user","content":"..."}],"system":"optional"}
const MODEL = "@cf/meta/llama-3.1-8b-instruct";
const cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Content-Type, Authorization", "Access-Control-Allow-Methods": "POST, OPTIONS" };
const json = (d, s = 200) => new Response(JSON.stringify(d), { status: s, headers: { "Content-Type": "application/json", ...cors } });
export default {
async fetch(req, env) {
if (req.method === "OPTIONS") return new Response(null, { headers: cors });
if (env.API_KEY && req.headers.get("Authorization") !== `Bearer ${env.API_KEY}`) return json({ error: "unauthorized" }, 401);
if (req.method !== "POST") return json({ error: "POST /chat" }, 405);
let body; try { body = await req.json(); } catch { return json({ error: "invalid JSON" }, 400); }
if (!body.message || typeof body.message !== "string") return json({ error: "message (string) required" }, 400);
const history = Array.isArray(body.history) ? body.history.slice(-20) : [];
const messages = [{ role: "system", content: body.system || "You are a helpful, concise assistant." }, ...history, { role: "user", content: body.message }];
const out = await env.AI.run(env.MODEL || MODEL, { messages, max_tokens: body.max_tokens || 512 });
return json({ reply: out.response, model: env.MODEL || MODEL });
},
};
04-translate/wrangler.toml:
name = "cf-translate"
main = "worker.js"
compatibility_date = "2026-10-01"
[ai]
binding = "AI"
04-translate/worker.js:
// Translation endpoint using Meta m2m100 (100 languages)
// POST {"text":"Hello","target_lang":"french","source_lang":"english"} or GET /?text=Hello&to=spanish
const MODEL = "@cf/meta/m2m100-1.2b";
const json = (d, s = 200) => new Response(JSON.stringify(d), { status: s, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" } });
export default {
async fetch(req, env) {
let text, to, from;
if (req.method === "GET") { const u = new URL(req.url); text = u.searchParams.get("text"); to = u.searchParams.get("to"); from = u.searchParams.get("from"); }
else { try { ({ text, target_lang: to, source_lang: from } = await req.json()); } catch { return json({ error: "invalid JSON" }, 400); } }
if (!text || !to) return json({ error: "text and target_lang required" }, 400);
const o = await env.AI.run(MODEL, { text, target_lang: to, source_lang: from || "english" });
return json({ translated_text: o.translated_text, target_lang: to });
},
};
Put each pair of files in its own folder (or unzip the free download), then:
cd 01-chat-api
npx wrangler login
npx wrangler deploy
Repeat in 04-translate.
curl -X POST https://<your-chat-worker>.workers.dev \
-H "Content-Type: application/json" \
-d '{"message":"Explain edge functions in one sentence"}'
curl "https://<your-translate-worker>.workers.dev/?text=Hello%20world&to=french"
workers.dev URL burns through your free allocation quickly if someone finds it. The chat Worker checks an optional API_KEY secret (npx wrangler secret put API_KEY), then expects Authorization: Bearer <key>. test/run-tests.mjs, which calls Workers AI over REST with your account ID and token. Mocks wouldn't have caught model-output quirks.
CF_ACCOUNT_ID=... CF_API_TOKEN=... node test/run-tests.mjs
The free download is complete on its own. I also sell a fuller pack ($7) that adds an OpenAI-compatible /v1/chat/completions proxy with streaming (point the OpenAI SDK or LangChain at it), a batch summarizer, a JSON extractor with schema retry, semantic search with BGE embeddings, a prompt library, and a setup guide: https://cf-ai-starter-shop.pages.dev
Not affiliated with Cloudflare. Questions welcome in the comments.
Written with AI assistance.