Principals
Every Writ API key belongs to exactly one principal: an entry in your
tenant’s identity directory — a team, a service, an agent. Keys carry the
principal’s id, every check is enforced against it, and every receipt
snapshots it. The portal’s Principals page is the directory UI; the
/v1/principals endpoints are the API.
This is a different concept from the kill-switch “principal” (a sponsor/agent pair you revoke after an incident) — see the data dictionary.
Why principals exist
Without them, a key is just a string: revoking it is one click, but pausing a contractor’s access means hunting down every key they hold. Principals group keys by identity, so the human operations are one click:
- Disable a principal and every one of its keys gets DENY on the next check — until you re-enable it. No key surgery.
- Revoke all kills every key of the principal at once, each with its
own
REVOKEreceipt carrying your reason. Offboarding in one click. - Delete tombstones the principal: its keys are revoked first
(
?force=1), the record stays resolvable by id forever, and the name becomes reusable.
Naming
Names are unique per tenant, case-insensitively: Billing and billing
are the same name. 1–64 characters after trimming; letters, digits,
spaces, hyphens, underscores, and periods; at least one letter or digit.
Every tenant has a default principal. If you never create one, Writ
creates it the first time the directory is touched, and every key that
predates the directory is attached to it — so the API keys table’s
Principal column is never blank.
Issuing keys to a principal
Pass principal_id when you create a key. The id must belong to a live
principal in your tenant — an unknown, deleted, or disabled principal is
a 400, because a key must never be minted against an identity that can’t
authorize.
Portal-created keys land on default unless your integration passes
principal_id through the API.
Enforcement: fail closed
On every POST /v1/check, the gate resolves the key’s principal and:
- Deleted or missing principal → DENY (
"principal deleted"), receipted. - Disabled principal → DENY (
"principal disabled"), receipted. - Otherwise the check proceeds normally, and the receipt carries
principalId/principalName— the principal as it was at write time.
Renames and deletes never rewrite history: an old receipt always shows
the name the principal had when the decision was made, and the id in
principalId still resolves — tombstoned principals stay fetchable
forever.
Managing the directory
Directory mutations write their own receipts — PRINCIPAL_CREATE,
PRINCIPAL_UPDATE, PRINCIPAL_DELETE — so the audit log shows not just
what the agents did, but who was allowed to exist when they did it.
For agents
If your agent manages Writ tenants programmatically: resolve the
principal once, store the principalId (stable across renames), and pass
it as principal_id at key creation. When you offboard an identity, call
POST /v1/principals/{id}/revoke — one call, every key dead, every
revocation receipted with your reason.
Per-principal policy overrides
A principal can carry its own policy override — the top layer of the
policy precedence: it beats the environment override, which
beats the tenant default. Unset verbs inherit downward, and the
dashboard’s Policy page shows, per verb, where the effective mode comes
from. Only live principals can carry policy: PUT /v1/policies/principals/{id} on a tombstoned or disabled principal is
400.
Disabling or deleting a principal makes its override inert — it never supplies a mode while the principal can’t authorize checks, and re-enabling re-arms it. The override data is kept, not wiped.