> 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. # Data dictionary > Every object Writ stores — tenants, keys, principals, policies, grants, receipts — and what each field means. The complete inventory of what Writ keeps about you and your agents. If an object isn't listed here, Writ doesn't store it. A standing rule: **raw secrets are never stored.** API keys, sponsor tokens, and magic-link tokens are kept as SHA-256 hashes only. The raw value is shown exactly once, at creation, and then it's gone. ## Tenant Your account. One tenant per email address. Everything below belongs to a tenant. | Field | Meaning | | -------------------- | ------------------------------------------------------- | | `id` | `ten_…` — the tenant's unique id | | `name` | Human-readable name (defaults to your email domain) | | `email` | The address the tenant was created from | | `plan` | `free`, or flipped by the Stripe webhook after payment | | `policy` | The tenant's policy (see Policy) | | `stripe_customer_id` | Set once a Stripe customer exists for this tenant | | `pending_welcome` | True until the first API key + sponsor token are issued | | `created_at` | Unix timestamp | ## API key Authenticates calls to the gate. Looks like `writ_…`. A tenant can have many. | Field | Meaning | | -------------------------------- | ------------------------------------------------------------------------------------------- | | `key_hash` | SHA-256 of the key. The raw key is never stored | | `prefix` | Short non-secret display prefix (e.g. `writ_AbC1`) so you can tell keys apart in the portal | | `label` | Optional label you set at creation | | `email` | Address it was issued for | | `tenant_id` | Owning tenant | | `plan` | Mirrors the tenant plan at issue time | | `checks_used` / `receipts_count` | Usage counters for the current period | | `period` / `usage_since` | The billing period the counters belong to | | `created_at` | Unix timestamp | | `revoked_at` | Set when revoked. Revoked keys stop authenticating immediately; the record is kept | Lost your key? While signed in to the portal you can mint a new one (API keys → + New key) and revoke the lost one. No need to delete anything. ## Sponsor token The human half of the credential pair. Looks like `writ_sp_…`. **One per tenant**, minted when the tenant is created, shown once, never recoverable, never re-issued through the portal today. It authorizes the human-side operations: creating grants, revoking and reinstating principals. The API key says *which tenant is asking*; the sponsor token says *a human approved this*. ## Principal An agent acting on someone's behalf: a **sponsor** (the human or system that approves) plus an **agent** (the software doing the work), within a tenant. Principals come into existence implicitly — the first time a grant names them — and can be revoked pre-emptively. | Field | Meaning | | ------------ | --------------------------------- | | `sponsor_id` | Who approved this agent's actions | | `agent_id` | The agent itself | | `tenant_id` | Owning tenant | ## Policy **One per tenant.** A table mapping each verb to a mode. There is no per-agent or per-environment policy today. | Mode | Meaning | | --------------- | ---------------------------------------------- | | `allow` | The check passes without further approval | | `deny` | The check fails; the write must not run | | `require_grant` | The check passes only with a live grant | | `step_up` | A human sponsor must approve via a grant first | The verb set is fixed in the gate (money movement, identity, data changes, communications, infrastructure, and others). Custom verbs are not supported today. ## Grant A short-lived permission slip minted by a human (sponsor token or dashboard) after a `STEP_UP`, or directly via `POST /v1/grants`. | Field | Meaning | | --------------------------------------------------------- | -------------------------------------------------------------- | | `id` | `grnt_…` | | `sponsor_id` / `agent_id` / `verb` / `target` / `purpose` | Exactly what is allowed. All five must match the check | | `ttl_seconds` | Lifetime. Defaults to 90 seconds, max 3600 (1 hour) | | `expires_at` | Unix timestamp when it dies | | `spent` | Single-use semantics: it can't be reused for a different write | | `tenant_id` | Owning tenant | | `created_at` | Unix timestamp | ## Receipt Written for **every** decision the gate makes — `ALLOW`, `DENY`, `STEP_UP`, `REVOKE`, and others. Denials write receipts too: the refusal itself is auditable. Receipts are the audit log, and the audit log is the meter. | Field | Meaning | | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `receiptId` | `rcpt_…` | | `tenantId` | Owning tenant | | `decision` | The gate's answer | | `sponsorId` / `agentId` / `verb` / `target` / `purpose` | What was decided on | | `grantId` | The grant that authorized it, if any | | `reason` | Human-readable reason for the decision | | `policyVersion` | The policy version the decision ran against | | `prevHash` / `chainHash` | Tamper-evident chain: each receipt stores the previous receipt's `chainHash` (or `GENESIS`), and `chainHash = HMAC(canonical fields + prevHash)` with a tenant-specific key | | `createdAt` | Unix timestamp | Verify any receipt chain with `GET /v1/receipts/verify` or `./writ verify-chain`. ## Revocation The kill switch. Revoking a principal denies every subsequent check for that sponsor/agent pair, immediately. | Field | Meaning | | ------------------------- | --------------------------- | | `sponsor_id` / `agent_id` | The principal being cut off | | `reason` | Why, in your words | | `revoked_at` | Unix timestamp | | `tenant_id` | Owning tenant | Reinstate undoes it; the principal can check again. ## Magic-link token Single-use sign-in token, emailed to you. Stored as a SHA-256 hash with a 15-minute expiry; the raw token travels in the URL fragment so it never lands in server logs. The account-creation variant works the same way and creates the tenant only when clicked. ## Rate limits Unauthenticated endpoints are bounded before any account lookup, so the limits themselves can't be used to probe for accounts: 30 magic-link requests per hour per IP, a per-email cooldown on magic links, and per-tenant caps on key creation. > Every object Writ stores — tenants, keys, principals, policies, grants, receipts — and what each field means.