// 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.

V8 isolatesDeno.serveKV storeCronSecretsVersionsEgress-isolated~35ms cold start

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:

bash
# 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

Entry point
Your `code` is the index.ts entry. Export a Deno.serve(handler) — that’s the whole contract.
Multi-file
Add helpers via the `files` map (path → content). index.ts imports them with relative paths.
TypeScript
TS runs as-is — no build step. Types are erased at load.
Remote imports
import anything from https://esm.sh/… or deno.land — fetched + cached on first run.
Secrets
Set `env`; read with Deno.env.get(). Sealed at rest, decrypted only in the isolate.
Egress-isolated
Functions reach the public internet + the KV binding, but NOT your internal services. SSRF-safe by construction.
KV binding
OLLANODE_KV_URL + OLLANODE_KV_TOKEN are injected automatically — a per-project key/value store with no credential to manage.
API binding
OLLANODE_API_URL + OLLANODE_API_TOKEN are injected too: call /v1/... for your own project with no API key. Read-only by design.
Auth gate
Set require_auth and the edge rejects unkeyed calls before your code runs — no auth boilerplate inside the function.
Response caching
Send a Cache-Control header and the edge caches that response. Opt-in: send nothing and every call runs your code.

// API reference

Every operation is one call

Base URL https://api.ollanode.com. Authenticate with Authorization: Bearer <api-key> (or a dashboard session token).

POST /v1/functions functions:write
Create + deploy a function. 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\" } }))"
  }'
Response
{ "id": "fn_019f6cb5dfd9...", "name": "hello", "active": true,
  "files": {}, "env_keys": [], "limits": {}, "schedule": null,
  "created_at": "2026-07-17T03:47:54Z" }
GET /v1/functions functions:read
List every function in the project (paginated).
curl https://api.ollanode.com/v1/functions?limit=100 \
  -H "authorization: Bearer $OLLANODE_API_KEY"
GET /v1/functions/:id functions:read
Fetch one function, including its current code + config.
curl https://api.ollanode.com/v1/functions/$FID \
  -H "authorization: Bearer $OLLANODE_API_KEY"
PATCH /v1/functions/:id functions:write
Update + redeploy. Send any of 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"]}'
DELETE /v1/functions/:id functions:delete
Delete a function. Edges drop it immediately.
curl -X DELETE https://api.ollanode.com/v1/functions/$FID \
  -H "authorization: Bearer $OLLANODE_API_KEY"
GET /v1/functions/:id/versions functions:read
Deploy history, newest first. Each 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"
Response
[ { "version": 1784235380545, "created_at": "2026-07-16T20:56:20Z" },
  { "version": 1784235375479, "created_at": "2026-07-16T20:56:15Z" } ]
POST /v1/functions/:id/rollback functions:write
Republish a stored version as the new current revision. Pass the exact 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}'
GET /v1/functions/:id/stats functions:read
Live invocation metrics: count, error rate, average latency, egress bytes.
curl https://api.ollanode.com/v1/functions/$FID/stats \
  -H "authorization: Bearer $OLLANODE_API_KEY"
Response
{ "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 }
GET /v1/functions/:id/runs functions:read
Scheduled (cron) run history — status + duration for each fire, newest first.
curl https://api.ollanode.com/v1/functions/$FID/runs \
  -H "authorization: Bearer $OLLANODE_API_KEY"
Response
[ { "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 } ]
GET /v1/functions/:id/logs functions:read
Recent 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"
Response
[ { "time": "2026-07-17T04:12:01.933Z", "message": "handling request /x" } ]
GET|POST /__fn/:id public
Invoke the function. Anything after the id is passed through as the request path. No auth required (the function decides its own auth).
curl "https://api.ollanode.com/__fn/$FID/any/path?q=1"
Response
{ "ok": true }
PUT|GET|DELETE /v1/kv/:key x-kv-token
Per-project key/value store. Functions reach it with the auto-injected OLLANODE_KV_URL + OLLANODE_KV_TOKEN (no key management). Values up to 256KB.
// 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:

POST /v1/functions — full body
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 * * * *"
  }'
Versions & rollback

Every deploy is stored. GET /versions lists epoch-millisecond ids; roll back with the exact id (not a sequential index).

Scheduled (cron)

Set a 5-field cron in schedule. The platform invokes the function on that cadence (checked every minute).

Observability

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.

Auth gate — no auth code in your function
# 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>
Edge caching — opt in with Cache-Control
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

Egress isolation
Functions run on an isolated network. They can call the public internet and the KV binding, but cannot reach your database, object store, or other services.
Per-function rate limit
Each function is capped at 100 req/s (burst 200). Sustained abuse returns HTTP 429 — one hammered function can’t starve the shared runtime.
WAF passthrough
The OWASP-CRS WAF fronts the API but treats /v1/functions as detection-only, so legitimate code (which may contain SQL/HTML) is never falsely blocked.
Agent approval gating
# 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.

01 Signed playback URLs auth
Mint short-lived, HMAC-signed video URLs at the edge so links can’t be shared or hotlinked.
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}\` });
02 Webhook receiver → Slack integrate
Accept a provider webhook, transform it, and fan out to Slack / Discord / email.
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");
03 Feature flags & A/B kv
Serve config / flags / experiment buckets from KV without a redeploy.
const flags = JSON.parse(await kvGet("flags"));
return Response.json({ newPlayer: flags.newPlayer ?? false });
04 Geo routing & blocking geo
Read Cloudflare’s CF-IPCountry header to localize, route, or geo-block — free, no lookup service.
const cc = req.headers.get("cf-ipcountry");
if (["RU","KP"].includes(cc)) return new Response("blocked",{status:451});
return Response.json({ country: cc });
05 Header rewriting proxy
Inject CORS, security, or cache-control headers on responses from an origin.
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 });
06 API gateway / BFF proxy
Aggregate several backend calls into one edge response tailored to your frontend.
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 });
07 Per-key rate limiting kv
Count requests per API key / IP in KV and throttle abusers before they hit your origin.
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});
08 URL shortener kv
Map short slugs to long URLs in KV and 302 at the edge.
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 });
09 Video metadata / OG API video
Return dynamic Open-Graph / oEmbed JSON for a video so social cards render correctly.
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" });
10 JWT auth gateway auth
Validate a JWT (via a remote-imported lib) and gate access to protected content.
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 });
11 Scheduled cache warming cron
A cron function that pre-fetches hot manifests so viewers never hit a cold cache.
// schedule: "*/10 * * * *"
Deno.serve(async () => {
  for (const id of hotIds) await fetch(cdn + "/v/" + id + ".m3u8");
  return new Response("warmed");
});
12 Form / lead capture kv
Accept a form POST, store it in KV, and notify — no backend to run.
const f = await req.formData();
await kvPut("lead:" + Date.now(), JSON.stringify(Object.fromEntries(f)));
return Response.redirect("/thanks", 303);
13 Playlist / manifest rewrite video
Rewrite an HLS/DASH manifest per viewer — inject ads, reorder renditions, or watermark.
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" } });
14 Analytics beacon kv
Collect lightweight play/heartbeat events into KV counters, poll them from /stats-style endpoints.
const { videoId } = await req.json();
await kvIncr("plays:" + videoId);
return new Response(null, { status: 204 });
15 Localized responses (i18n) geo
Pick a language from Accept-Language / country and return localized copy.
const lang = (req.headers.get("accept-language")||"en").slice(0,2);
return Response.json(strings[lang] ?? strings.en);
16 Coupon / license check kv
Validate and atomically redeem coupon or license keys stored in KV.
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");
17 JSON transform / filter proxy
Reshape or field-filter an upstream JSON payload so clients download less.
const data = await fetch(api + "/videos").then(r => r.json());
return Response.json(data.items.map(v => ({ id: v.id, title: v.title })));
18 Bot / UA filtering auth
Block or challenge suspicious user-agents before they reach your origin.
const ua = req.headers.get("user-agent") || "";
if (/curl|scrapy|bot/i.test(ua)) return new Response("no", { status: 403 });
19 Scheduled data sync cron
A heartbeat/cron function that syncs an external source into KV on a schedule.
// 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);
});
20 Status aggregator proxy
Probe several services and return one health JSON for an uptime page.
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

Resource
Default
Notes
Memory
150 MB
per invocation; configurable via limits.memory_mb
Wall-clock timeout
30 s
limits.timeout_ms
CPU time
5 s soft / 10 s hard
limits.cpu_soft_ms / cpu_hard_ms
Request rate
100 req/s
per function, burst 200 → HTTP 429
Source size
2 MB total
index.ts + all files, max 64 files
KV value
256 KB
per key; keys up to 512 bytes
Cold start
~35 ms
warm invocations ~2 ms

Deploy your first function.

Create an API key in the dashboard and ship JavaScript to the edge in minutes.

Open the dashboard →