The problem: an API that does not know who is asking
Every later check in Parmana answers a question about an action. Is this refund allowed by the policy in effect? Did a trusted person sign an approval for this order and this amount? None of those answers says which system sent the request. Without that, three things break. You cannot limit what one caller may ask for. You cannot slow down one caller without slowing everyone. And when something goes wrong, the record says what was tried but not by whom.The idea: the door knows, or the door stays shut
The caller layer has one rule: if Parmana cannot establish and record who is calling, it does not proceed. Not at startup, not on a request, not when the record cannot be written.How Parmana does it
One key per caller, sent as a bearer token. Every route needs it except a short list: the health and readiness probes, the API documentation, and the routes that verify a signed record, which anyone holding a record should be able to use without an account. The server keeps only a hash. Each key’s entry holds a SHA-256 hash of the key, never the key itself, and the comparison runs in constant time. Records, responses and audit events name the caller by its caller id. A leaked configuration file gives an attacker hashes, not keys. No silent default. In the code, the option that mounts caller authentication is required. Every place that builds the API must say either “here is the authenticator and the audit sink” or, literally, “disabled”. There is no third choice where forgetting means open. Refuse to start. A production server with no keys configured does not start, unless an operator sets the switch that turns authentication off, explicitly. When that switch is on, the readiness check reports it in its own response, with a warning, so monitoring that already polls readiness can alert on a field rather than hope someone reads a log. The public sandbox refuses to start with it at all. Refuse to run unrecorded. Every authentication outcome, accepted or rejected, is written to the caller audit trail before the request goes further. If that write fails, the request fails with503 AUDIT_UNAVAILABLE. No retry, no buffer, no queue. A valid key whose “accepted” event cannot be stored gets the 503, not the 200 it would otherwise get. I accepted that availability cost on purpose: an action that runs with no record of who asked contradicts the point of the product.
Count the failures. Each rejected key writes a signed audit event, and in production signing is a call to a key service. So a stranger sending bad keys could spend the signing capacity real requests need. Since 2026-10-03, failed authentications are counted per client address, 30 a minute by default, and only failures count.
What it does not do
- A key is not an approval. It says which system is calling. Every agent action, reads included, still needs a policy that allows it and a signed approval from a trusted person, checked after the key.
- Whoever holds the key is the caller. A bearer key proves possession, not intent. A key printed in a build log is that caller until the operator removes it.
- Keys are managed by hand. There is no API to issue, rotate or revoke a key. The operator generates one with a script, adds its entry to the server’s configuration and redeploys. Revoking means removing the entry and redeploying.
- Switching authentication off removes identity, not authorization. With the switch on, nobody knows who asked, but the policy, the bound facts and the signed approval are still checked. It is meant for local development and tutorials only.
- Failing closed has a price. A database outage that stops audit writes stops every authenticated request. That is the design, and it is still an outage.
- The per-address limit depends on the platform. How the hosting platform reports client addresses decides how well that limit works, and I have not checked it live.