API reference · Management plane
Edge functions
Deploy server-side code that runs on its own subdomain, holds a secret key of its own, and talks to its project's API over HTTP. How to write one is in the edge function guide.
Host: https://api.manybases.com · Key: ak_. No signed-in human is required for any of the function routes; one is required to set a secret's value.
The model#
A function is three things: a script — one bundled ES module with export default, bundled on your machine and sent as text —, a secret key minted for it and injected into the script as a secret variable, and a subdomain: https://proj-<id>-fn-<slug>.manybases.com. There is no table of functions: the list is derived from the live fn:<slug> keys of the project.
A function's code never holds more authority than the secret key of its own project. It has no direct access to the database or the file store: everything it reads or writes goes over HTTP to its project's API, carried by the injected key, and passes through the same checks as any other request. The key's value is never seen — not by the agent, not by a human, not by any screen.
The script receives two environment variables: MANYBASES_URL (its project's API, https://proj-<id>.manybases.com/v1) and MANYBASES_SECRET_KEY (its key). Client secrets are added by name — see below.
The four routes#
/v1/projects/:pid/functionslist_functions{ slug, url, key_id, key_created_at, log_key_ids, rate_limit, calls_today }. Never the key value, never the source. prefix and last_used_at appear only with a signed-in human/v1/projects/:pid/functions { slug, module_source, vars?, limits?, rate_limit? }deploy_function201 with the key card, without its value. Redeploying a slug replaces the script and force-rotates its key. A vars value shaped like a known provider key is refused with a 400 naming the variable, never the value — keys are secrets/v1/projects/:pid/functions/:slug/rotate-secretrotate_function_secret401s/v1/projects/:pid/functions/:slugdelete_functionkey_created_atis the date of the key, which a rotation renews; it is not a deployment date.log_key_idslists every generation of the function's key, revoked ones included, newest first, capped at 50 generations: pass them to the request log askey_id=in.(…)to read the calls this function made to its project. Redeploying is a rotation, so filtering on the live key alone would cut off everything before the last deployment. Each generation appears under two ids: itsid, which the log records since the fix of 2026-09-19, then the id the key service gave it, which the log recorded before.module_sourceis capped at 1 MB →413.varsare plain-text values; names the product injects or reserves are refused by name. Asecretsfield in this body is refused with a400that names it: values are set through the secrets routes.limitsare per invocation:{ cpu_ms, subrequests }, defaults 10 000 ms of CPU and 100 subrequests.cpu_msis capped at 10 000: above it, deploying answers400 validation_errorand nothing is deployed. A function that exhausts them answers503 internal; an unknown slug answers the neutral404; a function whose code throws answers500 internalwith a message saying the deployed code failed.rate_limitsets the function's call limits:{ per_visitor_per_minute?, per_visitor_per_day?, per_function_per_day? }, integers from 1 up to 6,000 a minute and 1,000,000 a day. Defaults: 60 a minute and 300 a UTC day from one address, 3,000 a UTC day for the function. Sent, it replaces the whole policy and an omitted field takes its default —{}restores the defaults; omitted, the policy in place stays, and a first deployment gets the defaults. An invalid value answers400 validation_errorand nothing is deployed. Beyond a limit a call answers429withRetry-After, before the code runs. The list gives each function'srate_limitandcalls_today— the calls admitted since 00:00 UTC, refusals not counted; both arenullwhen the counter cannot be read.- The slug is 1 to 28 characters,
a-z0-9, hyphens inside only — it is a DNS label.secretsis reserved. - Nothing caps the number of functions of an account.
After a deletion the registry is authoritative at once, and the function no longer runs: a call that arrives after the deletion's response answers the neutral 404, even when a visitor keeps calling. A call that arrived before may still finish. Redeploying the same slug brings the function back without waiting, with the project's secrets.
Secrets#
A client secret (an API key of a third-party service, say) is set once per project, or once for the whole account — see Shared secrets. Either way every function of the project receives it, including the ones deployed later, and it survives redeployments; a project secret masks a shared one of the same name. The value passes through one channel — the dashboard, under a signed-in human. An agent reads the names already set (list_secrets), writes code that reads env.NAME, deploys, asks the human to set any missing value, and checks by the names actually present on its function.
/v1/projects/:pid/secretslist_secrets{ name, updated_at }, sorted. digest (the SHA-256 of the value, to check a paste without revealing it) appears only with a signed-in human — list_secrets never relays it. Each entry carries its source, project or shared; a shared one also carries note, available and, when it is not delivered, reason, and a project one carries overrides_shared when it masks a shared secret of the same name/v1/projects/:pid/secrets/:name { value }functions: [{ slug, applied }]. Works on a project with no function yet/v1/projects/:pid/secrets/:namerestored_shared), and the name removed from the others. A name that is only shared is removed from no function — it is removed on the account's screen, and the response says so (shared)/v1/projects/:pid/functions/:slug/secretslist_function_secretsMANYBASES_URL and MANYBASES_SECRET_KEY are filtered out. Each name carries its source: project, shared, or residue when neither registry holds it.- A name follows
[A-Za-z_][A-Za-z0-9_]{0,63}. The wholeMANYBASES_prefix is reserved. - A value is at most 5 KB →
413. - Replaying a
PUTpushes the value onto every live function again, one after the other, and only repairs a transient failure. Two platform refusals last as long as their cause, and no replay changes them: the function already carries the name in itsvars; or the name would be its 129th variable — it already holds 128,varsand secrets alike, the two Manybases injects included. It then staysapplied: false, and its next deployment fails until the cause is removed — for the name, drop that variable fromvarsor remove the secret; for the room, drop a variable fromvarsor a project secret. A function whose script is missing — a deployment or a deletion left halfway, its key still live — does not get the value from a replay either, until it is redeployed or deleted. - Values are encrypted at rest, and the encryption is bound to the project and the name: a row copied to another project or another name does not decrypt. No route ever returns a value.
- A secret set while a deployment is in flight is not lost — the registry holds it and the next deployment injects it — but a function deployed during the write may serve the previous value (or
undefined) until thePUTis replayed or the function redeployed. The registry is the product's truth; the list on a function is the edge's truth. - Deleting a function does not delete the project's secrets. Deleting the project does.
Keys and emergency stops#
The fn:<slug> key of a function appears in the project's key list. Revoking it through DELETE /v1/projects/:pid/keys/:kid needs no human and is the finest emergency stop the product has; rotating it through the generic rotate route is refused with 403 — use rotate-secret.
