Skip to content

Writing serverless functions

A serverless function is your own code running on the accelerator. Reach for one when something can’t live in the client: a secret you can’t ship to the browser, a call to a third-party API, server-side validation, or logic an agent calls as a tool. This page is the authoring playbook — the client.functions reference has the full management API.

A function is a single ES module that default-exports a fetch handler — the same shape as the request you’d write for any edge runtime:

export default {
async fetch(request) {
const { name } = await request.json().catch(() => ({}));
return Response.json({ greeting: `Hello, ${name ?? "world"}!` });
},
};

It owns its own routing and CORS — branch on new URL(request.url).pathname and request.method to expose multiple routes from one function.

Deploy with the SDK or the CLI. The name is a DNS-safe slug — it becomes the function’s subdomain label.

const { config } = await client.functions.deploy(appId, {
name: "greet",
displayName: "Greeter",
code: greetSource, // the module above, as a string
triggers: { http: { enabled: true } },
});
// Reachable at https://greet--<app-slug>.fns.<zone>/

client.functions.invoke calls an HTTP-triggered function and parses its JSON. The SDK attaches the caller’s credentials automatically — the app key, and the user’s session when one is signed in — so you don’t assemble the URL or wire up auth headers yourself:

const out = await client.functions.invoke<{ greeting: string }>(
{ name: "greet", slug: "myapp" },
{ body: { name: "Ada" } }, // a body implies POST
);

The platform passes the session token through to the function. To act as the user, verify it server-side and trust nothing the client claims:

const token = request.headers.get("X-Muhkoo-Session");
const verify = await fetch("https://api.muhkoo.dev/api/auth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
if (!verify.ok) return new Response("Sign in required", { status: 401 });
const { commitment } = await verify.json(); // the verified caller

This is the core “authenticated proxy” pattern: the browser calls the function with its session, the function verifies it and then uses a secret (an admin key, a third-party token) that never reaches the browser. Bake secrets in at deploy time rather than shipping them client-side.

A function can also run on messages in a Space — the same model as an agent, but your code is the brain. It receives the decrypted message; a non-empty response body is posted back as a reply.

await client.functions.update(appId, fnId, {
triggers: { http: { enabled: true }, space: { match: [{ type: "keyword", pattern: "/weather" }] } },
});
await client.functions.enable(appId, fnId, channelSpaceId); // per-Space opt-in
// In the function, handle the Space event:
if (request.headers.get("X-Muhkoo-Event") === "space.message") {
const { text } = await request.json();
return Response.json({ reply: `You said: ${text}` }); // posted to the Space
}

Every function exists independently in test and production — the same way hosting and config do. Which one you touch is set by the environment of the key you deploy with (mk_test_sk_* → test) or the portal’s env selector. Iterate in test, then promote test → production in place once you’re happy. See Environments.

  1. Deploy to test and exercise it at <name>--<slug>.test.fns.<zone>.
  2. When it’s right, promote — production functions are upserted by name.
  3. Production traffic is never touched until you promote.