Authentication

Writ has two credentials, held by two different parties. The API key says which tenant is asking. The sponsor token says a human approved this. Most integrations only ever touch the API key; the sponsor token belongs to the human in the loop.

API keys

An API key looks like writ_... and belongs to a tenant. It authorizes:

  • POST /v1/check — the commit-time decision
  • POST /v1/tokens/verify — auth token validation
  • GET /v1/policy, PUT /v1/policy — the tenant’s verb policy
  • GET /v1/me — tenant and key identity
  • GET /v1/receipts* — reading, streaming, and verifying the audit log
  • POST /v1/tenant/invites — team invite codes

Issue one with no auth at all:

curl -X POST https://j72ckh66ukck2kcbq3oiwxaalm0olxwb.lambda-url.us-east-1.on.aws/v1/keys \
-H 'Content-Type: application/json' \
-d '{"email": "ops@example.com", "tenantName": "Acme support"}'

The first key for an email creates the tenant and returns the tenant’s sponsorToken — shown exactly once, never recoverable. Save it immediately; there is no “resend my sponsor token.” Later keys for the same email (or keys issued via an invite code) join the existing tenant and do not return a sponsor token.

Then call the API:

curl https://j72ckh66ukck2kcbq3oiwxaalm0olxwb.lambda-url.us-east-1.on.aws/v1/me \
-H 'Authorization: Bearer writ_KEY'

Key issuance is rate-limited to 10 keys per hour per client IP.

A sponsor token looks like writ_sp_... and belongs to the tenant’s human. It authorizes the human-approval half of the product:

  • POST /v1/grants — approve one write (the STEP_UP flow)
  • POST /v1/revoke — the kill switch
  • POST /v1/reinstate — undo a revocation
  • GET /v1/revoked — the kill-switch roster

It goes in the same Authorization: Bearer header, on those endpoints only. API keys are rejected there and sponsor tokens are rejected everywhere else — the two credentials do not substitute for each other.

Treat the sponsor token like a password: it can approve money movement and revoke agents. Store it in a secret manager, never in code or chat logs.

Tenants and teams

A tenant is the isolation boundary: one audit log, one policy, one set of revocations. A key only ever sees its own tenant’s receipts — there is no cross-tenant read path, and a foreign receipt ID is indistinguishable from a missing one.

To add teammates, create an invite code with your API key:

curl -X POST https://j72ckh66ukck2kcbq3oiwxaalm0olxwb.lambda-url.us-east-1.on.aws/v1/tenant/invites \
-H 'Authorization: Bearer writ_KEY'

Teammates pass the code as inviteCode to POST /v1/keys and land in your tenant. Creating a new code rotates the old one. Joining members never receive the sponsor token — there is one human credential per tenant, issued at creation.

The keyless sandbox

No signup, no key: POST /v1/sandbox mints a 90-second grant, but only for verb: "demo_write" on targets starting with demo-. It’s the trial loop — prove the intercept works, then issue a real key for real verbs.