> Call POST /v1/check before a tool that writes. DENY means do not invoke the tool. For clean Markdown of any page, append .md to the page URL. For the complete index, fetch https://docs.withwrit.com/llms.txt. # Principals > The identity directory — which team, service, or agent each API key belongs to, and how to retire them in one click. 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](/data-dictionary#principal-kill-switch). ## 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 `REVOKE` receipt 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. ```bash curl -X POST https://api.withwrit.com/v1/keys \ -H "Content-Type: application/json" \ -d '{"email":"ops@example.com","principal_id":"prn_9d2f4a1b2c3d"}' ``` ```json { "apiKey": "writ_7hK2xQ9mZp4v...", "tenantId": "ten_3fa8b91c2d44", "principalId": "prn_9d2f4a1b2c3d", "plan": "free" } ``` 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: 1. **Deleted or missing principal → DENY** (`"principal deleted"`), receipted. 2. **Disabled principal → DENY** (`"principal disabled"`), receipted. 3. 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 ```bash # create curl -X POST https://api.withwrit.com/v1/principals \ -H "Authorization: Bearer writ_KEY" -H "Content-Type: application/json" \ -d '{"name":"billing"}' # list (add ?include_deleted=1 to see tombstoned entries) curl https://api.withwrit.com/v1/principals \ -H "Authorization: Bearer writ_KEY" # rename / disable curl -X PATCH https://api.withwrit.com/v1/principals/prn_9d2f4a1b2c3d \ -H "Authorization: Bearer writ_KEY" -H "Content-Type: application/json" \ -d '{"name":"invoicing","disabled":true}' # revoke every key of the principal (each gets its own REVOKE receipt) curl -X POST https://api.withwrit.com/v1/principals/prn_9d2f4a1b2c3d/revoke \ -H "Authorization: Bearer writ_KEY" -H "Content-Type: application/json" \ -d '{"reason":"contractor offboarded"}' # delete (refuses with 409 while keys are live; ?force=1 revokes them first) curl -X DELETE "https://api.withwrit.com/v1/principals/prn_9d2f4a1b2c3d?force=1" \ -H "Authorization: Bearer writ_KEY" ``` 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](/policies): 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. > The identity directory — which team, service, or agent each API key belongs to, and how to retire them in one click.