Skip to main content
Every request carries one API key as a bearer token. The key says who is calling. It does not authorize an action: a policy and a signed human approval do that, after the key is accepted (How Parmana thinks).

Send a bearer key

Every route needs the header except these: A missing header, a header that is not Bearer <key>, or a key the server does not know returns 401 before anything else runs:
401
The server holds only a SHA-256 hash of each key and compares in constant time. Responses, records and audit events name the key by its callerId, never by the key itself.

Check your key: GET /callers/me

Call this first. It returns who the key is and what it may do, with the defaults below already applied. Real response from the sandbox with the demo key:
200
unrestrictedCapabilities is true only for a key granted every capability ("*").

What a key may do

Each key is one entry in the server’s PARMANA_API_KEYS (a JSON array). The entry, not the request, decides what the key may do: An agent’s key is usually allowedCapabilities with its few actions and nothing else. A person who approves policy or approver changes holds a separate USER key with a step up key (Credentials).

Where keys come from

The operator of the server issues keys. There is no API to create one. On a machine with the repository:
Other options: --allowed-principal-ids a,b, --credential-holder-type USER, and --generate-step-up-key (with USER). The script prints the raw key once, with its hash and the entry to add, and writes nothing to disk:
Give the raw key to the caller over a secure channel, add the entry to PARMANA_API_KEYS, and restart or redeploy the server. To revoke a key, remove its entry and redeploy. To rotate, add the new entry, move the caller, then remove the old one. The full procedure is in API keys and Deploy patterns. The server refuses to start with no keys configured, unless PARMANA_AUTH_DISABLED=true. That switch turns authentication off entirely, for local development and the tutorials only, and the server prints a warning at every start. Never set it on a real deployment. A sandbox refuses to start with it.

The sandbox demo key

The public sandbox, https://parmana-sandbox.vercel.app, has one published key: 2VfYWCzt_cBAPK-8uufX6ordfY2JuQhFPsohuEumKME. It is caller sandbox-visitor, may act only as itself, and may invoke only sandbox:receipt, an action that acts on nothing. It is not a human key, so governance routes refuse it with 403 NON_HUMAN_CALLER_DENIED. Every visitor shares it, so every visitor can read what others sent with it. It works only on the sandbox; production and every other deployment refuse it with 401. See Playground.

Rate limits

POST /execute is counted per key, not per address, because many agents call from one shared backend address. Over the limit, the request is refused before anything is evaluated, signed or recorded:
429
with a Retry-After header in seconds, and RateLimit headers on every limited response. Wait that long, then send again with a new businessTransactionId. With DATABASE_URL set, as on every hosted deployment, the counts are shared by all server instances, so the limit is the limit. Without it, each instance counts on its own.

Errors

Real bodies, from the sandbox. For each of these nothing is evaluated, approved or released. The 401 and 403 refusals are written to the caller audit trail; a 429 is refused before that. Fix the key or the request and send again. Every other error is in the Error catalog.

What a key does not prove

A valid key proves which system is calling. It does not prove who authorized the action: that is the signed Execution Authorization and the signed human approval, checked after the key. A request with a valid key is still refused when the policy says no, and an approved request is released only through the gateway, which checks its own signatures.