axon

Reference

One endpoint.

POST /api/v1/execute takes code, returns a JSON value. Everything else on this page is detail.

Endpoints

MethodPathPurpose
POST/api/v1/executeRun code, return a result.
GET/api/v1/metaLimits, engine version, license, sandbox properties.
GET/api/v1/examplesThe snippets used by the playground.
GET/healthLiveness. Does not execute anything.
OPTIONS*CORS preflight, Access-Control-Max-Age: 86400.

Request

{
  "code": "return 2 + 2",
  "timeout_ms": 750,
  "memory_mb": 16
}
FieldTypeDefaultRules
codestringrequired1 byte to 32 KB. A script body or an ES module.
timeout_msnumber750Clamped to 50..1500. An upper bound: the fixed CPU budget stops a runaway loop first.
memory_mbnumber8Clamped to 4..32. QuickJS heap.

A script body is wrapped in a function, so a top level return works. If the code contains import or export, it runs as a module. Both shapes are valid: export default value, or plain top level return with the imports hoisted above it. Neither needs a wrapper of your own.

Response

{
  "ok": true,
  "result": 4,
  "logs": [],
  "meta": {
    "modules": [],
    "engine": "quickjs 2025-09-13 (bellard)",
    "timeout_ms": 750,
    "memory_mb": 8
  }
}

result is whatever the code returned, serialised with JSON.stringify inside the sandbox. A returned undefined becomes null. Values that cannot be serialised return EXEC_ERROR. Results above 256 KB are refused. logs holds up to 50 captured lines from console.log, info, warn and error.

There is no server side timing field on purpose. Workers freezes its clock while a request is executing, so any in-sandbox duration would read 0. Time the call from your client, or read cpuTime and wallTime per event in wrangler tail --format json. The playground on the home page shows the client measured round trip.

Errors

Every error is JSON with ok: false, an error.code, an error.message and a request_id. Stack traces, wasm addresses and environment values are never returned.

CodeHTTPMeaning
BAD_REQUEST400Body missing, not JSON, not an object, or code is not a string.
PAYLOAD_TOO_LARGE413Body above 64 KB or code above 32 KB.
MODULE_NOT_FOUND422Package could not be resolved, or is a Node builtin.
EXEC_TIMEOUT408Instruction budget reached (about 280 ms of CPU), or the deadline passed.
MEMORY_LIMIT422QuickJS heap ceiling reached.
EXEC_ERROR422Your code threw, or its result cannot be serialised.
RATE_LIMITED429Per-IP bucket empty. Honour Retry-After.
BUSY5034 slots busy and the queue wait expired. Honour Retry-After.
INTERNAL500Unexpected failure. Treated as a bug, not a normal path.

Six ways in

1. curl, arithmetic

curl -s https://axon.va-n.com/api/v1/execute \
  -H 'content-type: application/json' \
  -d '{"code":"return Math.round(12000 * Math.pow(1.045, 7) * 100) / 100"}'

2. curl, regex

curl -s https://axon.va-n.com/api/v1/execute \
  -H 'content-type: application/json' \
  -d '{"code":"const s = \"ABC-142 ABC-157\"; return [...s.matchAll(/[A-Z]{3}-\\\\d+/g)].map(m => m[0])"}'

3. JavaScript, fetch

const response = await fetch("https://axon.va-n.com/api/v1/execute", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ code: "return new Date(0).toISOString()" }),
});
const data = await response.json();
console.log(data.result);

4. Python, requests

import requests

response = requests.post(
    "https://axon.va-n.com/api/v1/execute",
    json={"code": "return [1, 2, 3].reduce((a, b) => a + b, 0)"},
    timeout=10,
)
data = response.json()
print(data["result"])

5. A module, with lodash

{
  "code": "import _ from 'lodash';\nconst rows = [{team:'core', points:12}, {team:'web', points:7}, {team:'core', points:9}];\nreturn _.chain(rows).groupBy('team').mapValues(g => _.sumBy(g, 'points')).value();",
  "timeout_ms": 750
}

Imports are hoisted and the body is wrapped, so a plain top level return next to an import is the shortest form. export default value is accepted too. For large libraries, import the submodule (date-fns/addDays, not the whole date-fns barrel): a root barrel can expand to hundreds of modules, and the resolver stops at 10.

6. Business days between two dates

const end = new Date("2026-02-01");
let days = 0;
for (let d = new Date("2026-01-01"); d < end; d.setDate(d.getDate() + 1)) {
  const day = d.getUTCDay();
  if (day !== 0 && day !== 6) days++;
}
return days;

How the module loader works

The synchronous QuickJS build cannot await a network call from inside the sandbox, so resolution happens first, outside, and evaluation happens second, inside. The sandbox only ever sees a plain Map from module URL to source text.

Module resolution pipeline An agent posts code. The Worker scans import specifiers, fetches bundled packages from esm.sh into a cache and a source map, then starts a fresh QuickJS runtime that loads from the map only. agent POST code cloudflare worker router + limits import scanner esm.sh fetch + cache Map<url, source> no fs, no net no state esm.sh ?bundle one file per pkg cf cache 1 hour quickjs new runtime new heap sync loader deadline JSON out disposed POST fetch load

A module specifier is turned into https://esm.sh/<specifier>?bundle&target=es2022. The Worker follows up to two levels of imports, refuses anything over 10 modules or 1 MB, and rejects Node builtins before fetching. A package that cannot be resolved returns MODULE_NOT_FOUND with the specifier in the message, never a 500.

Self-host

Node 20 or newer, a Cloudflare account on the free plan, and a zone you control. No paid bindings are used.

git clone https://github.com/naas1201/axon
cd axon
npm install
npx wrangler deploy

Edit wrangler.toml to point routes at your own domain, or remove the block to use the *.workers.dev URL. The wasm is bundled at build time and served from the Worker, so there is no runtime download and no extra binding.

Run npm test for the lexer and admission-control tests, and node scripts/smoke.mjs https://your-domain for the live scenarios.

Headers

  • x-request-id on every response, also present as request_id in error bodies.
  • cache-control: no-store on all API responses.
  • retry-after on 429 and 503.
  • access-control-allow-origin: *, because agents run everywhere.