// Docs / Edge Functions
Run code at the edge.
Deploy JavaScript & TypeScript that runs in a
V8 isolate next to your CDN. Write a standard
Deno.serve(handler), we route
/__fn/<id> to it, and deploys propagate in seconds.
Runtime: Deno / V8 (Supabase Edge Runtime under the hood). It is JavaScript/TypeScript — there is no Rust/WASM function runtime; Rust is what the platform backend is written in.
// Quickstart
Zero to deployed in three calls
Create an API key in the dashboard, then:
# 1) deploy a function
FID=$(curl -s -X POST https://api.ollanode.com/v1/functions \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"name":"hello","code":"Deno.serve(()=>new Response(JSON.stringify({hello:\"from the edge\"}),{headers:{\"content-type\":\"application/json\"}}))"}' \
| jq -r .id)
# 2) invoke it (public, no auth)
curl https://api.ollanode.com/__fn/$FID
# -> {"hello":"from the edge"}
# 3) watch it live
curl https://api.ollanode.com/v1/functions/$FID/stats \
-H "authorization: Bearer $OLLANODE_API_KEY" // Runtime
How the runtime works
// API reference
Every operation is one call
Base URL https://api.ollanode.com. Authenticate with Authorization: Bearer <api-key> (or a dashboard session token).
code is the index.ts entry point; optional files, env, limits, schedule.curl -X POST https://api.ollanode.com/v1/functions \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "hello",
"code": "Deno.serve(() => new Response(JSON.stringify({ ok: true }), { headers: { \"content-type\": \"application/json\" } }))"
}' { "id": "fn_019f6cb5dfd9...", "name": "hello", "active": true,
"files": {}, "env_keys": [], "limits": {}, "schedule": null,
"created_at": "2026-07-17T03:47:54Z" } curl https://api.ollanode.com/v1/functions?limit=100 \
-H "authorization: Bearer $OLLANODE_API_KEY" curl https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" name, code, files, env, limits, schedule, active, require_auth. A new revision goes live in seconds. For secrets prefer env_patch (merge one key) / env_remove (drop keys) over env, which replaces the whole set.# redeploy new code
curl -X PATCH https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"code":"Deno.serve(()=>new Response(\"v2\"))"}'
# change ONE secret, leave the rest untouched
curl -X PATCH https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"env_patch":{"API_SECRET":"new-value"}}'
# drop a secret
curl -X PATCH https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"env_remove":["OLD_KEY"]}' curl -X DELETE https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" version is an epoch-millisecond id — use it verbatim for rollback.curl https://api.ollanode.com/v1/functions/$FID/versions \
-H "authorization: Bearer $OLLANODE_API_KEY" [ { "version": 1784235380545, "created_at": "2026-07-16T20:56:20Z" },
{ "version": 1784235375479, "created_at": "2026-07-16T20:56:15Z" } ] version id from /versions.curl -X POST https://api.ollanode.com/v1/functions/$FID/rollback \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"version":1784235375479}' curl https://api.ollanode.com/v1/functions/$FID/stats \
-H "authorization: Bearer $OLLANODE_API_KEY" { "invocations": 128, "ok": 126, "errors_4xx": 0, "errors_5xx": 2,
"rate_limited_429": 0, "error_rate": 0.0156, "avg_ms": 9.4,
"bytes": 48211, "last_seen_unix": 1784261220 } curl https://api.ollanode.com/v1/functions/$FID/runs \
-H "authorization: Bearer $OLLANODE_API_KEY" [ { "ran_at": "2026-07-17T03:50:03Z", "status": 200, "ms": 12, "error": null },
{ "ran_at": "2026-07-17T03:49:03Z", "status": 200, "ms": 9, "error": null } ] console.* output for this function, newest first. Add ?limit= (default 200, max 1000).curl "https://api.ollanode.com/v1/functions/$FID/logs?limit=50" \
-H "authorization: Bearer $OLLANODE_API_KEY" [ { "time": "2026-07-17T04:12:01.933Z", "message": "handling request /x" } ] curl "https://api.ollanode.com/__fn/$FID/any/path?q=1" { "ok": true } // inside a function:
const base = Deno.env.get("OLLANODE_KV_URL");
const tok = Deno.env.get("OLLANODE_KV_TOKEN");
await fetch(\`${base}/visits\`, { method: "PUT",
headers: { "x-kv-token": tok }, body: "42" });
const n = await (await fetch(\`${base}/visits\`,
{ headers: { "x-kv-token": tok } })).text(); // Configuration
Files, secrets, limits & cron
Everything a function needs is set on create or PATCH:
curl -X POST https://api.ollanode.com/v1/functions \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "worker",
"code": "Deno.serve(() => new Response(Deno.env.get(\"REGION\")))",
"files": { "util.ts": "export const v = 1;" },
"env": { "API_SECRET": "s3cr3t", "REGION": "det-03" },
"limits": { "memory_mb": 128, "timeout_ms": 5000,
"cpu_soft_ms": 2000, "cpu_hard_ms": 4000 },
"schedule": "*/5 * * * *"
}' Every deploy is stored. GET /versions lists epoch-millisecond ids; roll back with the exact id (not a sequential index).
Set a 5-field cron in schedule. The platform invokes the function on that cadence (checked every minute).
GET /stats returns invocations, error rate, avg latency and egress; GET /logs tails console output; GET /runs shows cron history. All three are on the dashboard too.
# turn the gate on; the response carries the key
curl -X PATCH https://api.ollanode.com/v1/functions/$FID \
-H "authorization: Bearer $OLLANODE_API_KEY" \
-H "content-type: application/json" \
-d '{"require_auth":true}'
# -> { ..., "require_auth": true, "auth_token": "fn_019f….<sig>" }
curl https://api.ollanode.com/__fn/$FID # 401
curl -H "x-fn-key: $KEY" https://api.ollanode.com/__fn/$FID # 200
# also accepted: Authorization: Bearer <key>, or ?key=<key> Deno.serve(async () => {
const data = await expensiveUpstream();
return new Response(JSON.stringify(data), {
headers: {
'content-type': 'application/json',
'cache-control': 'max-age=300', // <- edge caches for 5 min
},
});
});
// Send no cache-control and nothing is ever cached — every
// call runs your code. Check the X-Cache-Status response header. // Security & governance
Safe for humans and AI agents
# 1) An AI agent key hits a gated action -> 202, no change yet
curl -i -X POST https://api.ollanode.com/v1/functions \
-H "authorization: Bearer $AGENT_KEY" -d '{"name":"x","code":"..."}'
# HTTP/1.1 202 {"approval_id":"apr_...","status":"pending_approval"}
# 2) A human approves
curl -X POST https://api.ollanode.com/v1/approvals/$APR/decide \
-H "authorization: Bearer $OWNER_TOKEN" -d '{"decision":"approved"}'
# 3) The agent retries with the approval id -> 200, function created
curl -X POST https://api.ollanode.com/v1/functions \
-H "authorization: Bearer $AGENT_KEY" -H "x-approval-id: $APR" \
-d '{"name":"x","code":"..."}' // Patterns
Top 20 things to build
Real edge patterns — most need nothing but a function and the KV store.
const exp = Math.floor(Date.now()/1000) + 300;
const sig = await hmac(secret, \`${id}.${exp}\`);
return Response.json({ url: \`/v/${id}?e=${exp}&s=${sig}\` }); const e = await req.json();
await fetch(Deno.env.get("SLACK_URL"), { method:"POST",
body: JSON.stringify({ text: \`${e.type}: ${e.id}\` }) });
return new Response("ok"); const flags = JSON.parse(await kvGet("flags"));
return Response.json({ newPlayer: flags.newPlayer ?? false }); const cc = req.headers.get("cf-ipcountry");
if (["RU","KP"].includes(cc)) return new Response("blocked",{status:451});
return Response.json({ country: cc }); const r = await fetch(origin + new URL(req.url).pathname);
const h = new Headers(r.headers);
h.set("access-control-allow-origin", "*");
return new Response(r.body, { headers: h }); const [a,b] = await Promise.all([
fetch(api+"/videos/"+id).then(r=>r.json()),
fetch(api+"/analytics/"+id).then(r=>r.json())]);
return Response.json({ ...a, views: b.views }); const k = "rl:" + req.headers.get("x-api-key");
const n = Number(await kvGet(k) || 0) + 1;
await kvPut(k, String(n));
if (n > 100) return new Response("slow down",{status:429}); const slug = new URL(req.url).pathname.split("/").pop();
const dest = await kvGet("u:" + slug);
return dest ? Response.redirect(dest, 302)
: new Response("not found", { status: 404 }); const v = await fetch(api+"/videos/"+id).then(r=>r.json());
return Response.json({ title: v.title, thumbnail: v.poster,
duration: v.duration_secs, type: "video" }); import { verify } from "https://esm.sh/@tsndr/cloudflare-worker-jwt";
const ok = await verify(token, Deno.env.get("JWT_SECRET"));
if (!ok) return new Response("401", { status: 401 }); // schedule: "*/10 * * * *"
Deno.serve(async () => {
for (const id of hotIds) await fetch(cdn + "/v/" + id + ".m3u8");
return new Response("warmed");
}); const f = await req.formData();
await kvPut("lead:" + Date.now(), JSON.stringify(Object.fromEntries(f)));
return Response.redirect("/thanks", 303); let m = await fetch(origin + path).then(r => r.text());
m = m.replace(/#EXT-X-STREAM-INF.*\n.*1080.*\n/g, "");
return new Response(m, { headers: { "content-type": "application/vnd.apple.mpegurl" } }); const { videoId } = await req.json();
await kvIncr("plays:" + videoId);
return new Response(null, { status: 204 }); const lang = (req.headers.get("accept-language")||"en").slice(0,2);
return Response.json(strings[lang] ?? strings.en); const used = await kvGet("lic:" + key);
if (used) return new Response("used", { status: 409 });
await kvPut("lic:" + key, new Date().toISOString());
return new Response("valid"); const data = await fetch(api + "/videos").then(r => r.json());
return Response.json(data.items.map(v => ({ id: v.id, title: v.title }))); const ua = req.headers.get("user-agent") || "";
if (/curl|scrapy|bot/i.test(ua)) return new Response("no", { status: 403 }); // schedule: "0 * * * *" (hourly)
Deno.serve(async () => {
const rows = await fetch(Deno.env.get("SRC")).then(r => r.json());
await kvPut("catalog", JSON.stringify(rows));
return new Response("synced " + rows.length);
}); const checks = await Promise.all(urls.map(async u => ({
u, ok: (await fetch(u).catch(() => ({ ok: false }))).ok })));
return Response.json({ healthy: checks.every(c => c.ok), checks }); // Limits & quotas
Defaults you can raise
Deploy your first function.
Create an API key in the dashboard and ship JavaScript to the edge in minutes.
Open the dashboard →