> 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.