Access tokens
Users sign in with a zero-knowledge proof; machines use an access token. An access token is the sanctioned, non-ZK, machine-to-machine credential — a scoped, expiring key a script, server, CI job, or serverless function presents instead of running a login. It authenticates as the app (like an app key), never as a human user.
mk_<env>_at_<32 hex> │ │ │ └ at = access token └ live | testAn access token carries an explicit set of scopes (what it may do) and an expiry (when it stops working). It’s revocable at any time.
Scopes
Section titled “Scopes”Scopes are resource-level. A token is granted a subset of:
| Scope | Grants |
|---|---|
db:read / db:write | Read / write the app’s database tables |
kv:read / kv:write | Read / write app KV |
storage:read / storage:write | Read / write file storage |
messages:read / messages:write | Read / write space messages |
functions:invoke | Invoke serverless functions |
ai:infer | Run AI inference |
A request is rejected (403) if the token lacks the scope the route requires —
e.g. a db:read-only token cannot insert a row.
Creating a token
Section titled “Creating a token”Open your app → Access tokens → choose scopes, environment, and an expiry,
then Create. The mk_<env>_at_… secret is shown once — copy it now.
muhkoo tokens create <appId> \ --scopes db:read,db:write \ --env live \ --expires-in 90 \ --label ci-deploy
# list / revokemuhkoo tokens ls <appId>muhkoo tokens revoke <appId> <keyId>const { plaintext, keyId, expiresAt } = await client.accessTokens.create(appId, { scopes: ["db:read", "db:write"], env: "live", expiresInDays: 90, label: "ci-deploy",});// plaintext (mk_live_at_…) is returned once — store it as a secret now.
await client.accessTokens.list(appId);await client.accessTokens.revoke(appId, keyId);Using a token
Section titled “Using a token”An access token is presented on the same X-Muhkoo-Key header as an app key, so
it works anywhere a key does.
import { Client } from "@muhkoo/connect";
// Headless / server-side client — no ZK identity, just the token.const client = new Client({ accessToken: process.env.MUHKOO_ACCESS_TOKEN });
await client.db("orders").query({ where: [{ column: "status", op: "eq", value: "open" }] });accessToken takes precedence over apiKey when both are set.
curl https://api.muhkoo.dev/api/db/orders/query \ -H "X-Muhkoo-Key: mk_live_at_…" \ -H "Content-Type: application/json" \ -d '{"limit": 10}'Serverless functions get one automatically
Section titled “Serverless functions get one automatically”You don’t have to embed a token in a serverless function.
The platform mints a scoped access token per function and injects it as
env.MUHKOO_ACCESS_TOKEN (and mirrors it to env.MUHKOO_APP_KEY). That’s what
lets a function act as the server-side gateway to backend-only tables:
export default { async fetch(request, env) { const client = new Client({ apiKey: env.MUHKOO_APP_KEY, // the injected access token baseUrl: env.MUHKOO_API_URL, }); // your own authorization here, then reach sensitive data: const rows = await client.db("audit_log").query({ limit: 50 }); return Response.json(rows); },};Expiry & revocation
Section titled “Expiry & revocation”- Expiry — set at creation (default 90 days, up to 365). After it expires the token stops resolving; mint a new one.
- Revocation — revoke from the portal,
muhkoo tokens revoke, orclient.accessTokens.revoke(...). Revocation takes effect within the auth cache window (seconds), not at expiry. - Rotation — create the new token, deploy it, then revoke the old one.
Access tokens vs. app keys
Section titled “Access tokens vs. app keys”Publishable key (pk) | Secret key (sk) | Access token (at) | |
|---|---|---|---|
| Browser-safe | Yes | No | No |
| Scoped | No (full) | No (full) | Yes |
| Expires | No | No | Yes |
| Reaches backend-only tables | No | Yes | Yes (with db:*) |
Reach for an access token whenever a machine needs least-privilege, time-boxed access — CI, a server, or a function gating sensitive data.