> 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. # Custom verbs > Name the actions your business actually cares about — phi.export, trade.settle, patient.discharge — and put policy on them. Writ ships with a fixed set of built-in verbs (`refund`, `admit`, `prescribe`, `provision`, `verify`, `verify_human`, …). They cover common ground, but they are not your domain language. **Custom verbs** let you name the consequential actions your agents actually take — `phi.export`, `trade.settle`, `patient.discharge` — and put Writ policy on them, with the same checks, grants, receipts, and audit chain as everything else. Verbs attach to principals: a check always runs as a principal's key, and every receipt snapshots the principal. See [Principals](/principals). ## Defining a verb `POST /v1/verbs` with a `name` and a `description`: ```bash curl -X POST https://api.withwrit.com/v1/verbs \ -H "Authorization: Bearer $WRIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "phi.export", "description": "Agent exported PHI outside the enclave"}' ``` Names are lowercase `domain.action` style: 1–64 characters of letters, digits, dots, and underscores, with **at least one dot** — bare words are reserved for built-in verbs. A name may not collide with a built-in verb (case-insensitive) or an existing custom verb. A verb is usable in policies the moment it is created. The response is the verb record: ```json { "name": "phi.export", "builtin": false, "status": "live", "description": "Agent exported PHI outside the enclave", "aliasOf": null, "createdAt": 1759000000, "deprecatedAt": null } ``` Tenants are capped at 200 verbs (including deprecated ones). ## Setting policy on custom verbs Custom verbs are ordinary verbs everywhere else: `PUT /v1/policy` (and the per-environment overrides) accepts them the moment they exist, and `GET /v1/policy` lists the live ones in its `customVerbs` field. Unknown verbs still fail closed to `require_grant` — defining the verb is what promotes it from "deny by default" to a first-class policy target. ```bash curl -X PUT https://api.withwrit.com/v1/policy \ -H "Authorization: Bearer $WRIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"phi.export": "step_up"}' ``` ## Rename is safe Language changes. `PATCH /v1/verbs/{name}` renames a live verb (and/or updates its description), and the rename cannot silently drop protection: * The old name becomes a **deprecated alias** — checks that name it still resolve, and evaluate against the *current* verb's policy. Old code and old receipts keep their meaning. * Policies that used the old name are **migrated to the new name** — in the tenant default and in every environment override — so a rename can never silently drop a `deny`. Each touched policy's version bumps by one. * The old name is **rejected in new policies**, with the offending verbs listed in the 400 response. The receipt for a check that resolved an alias carries `resolvedVerb`: the verb whose policy actually decided. `verb` always echoes exactly what the caller sent. See the [data dictionary](/data-dictionary#receipt). ## Retire, don't delete `DELETE /v1/verbs/{name}` retires a verb: it becomes `deprecated`, checks that name it still resolve (its own policy still applies), and the name can never be reused or re-created — so old receipts stay interpretable. Retired verbs are rejected in new policies. Only live verbs can be retired. Fetch the full vocabulary — built-ins plus custom, each with its status and rename history — with `GET /v1/verbs`. The scanner reads this manifest to map its findings onto your vocabulary; see [Scanner](/scanner#preferring-your-vocabulary). > Name the actions your business actually cares about — phi.export, trade.settle, patient.discharge — and put policy on them.