KYA · Know Your Agent

An agent just charged your API $5,000.
Can you prove someone authorised it?

An API key proves something made the call. Writ proves who authorised it — a signed, revocable mandate from a real person, on every agent action, in one verify call. 1,000 verifications a month free.

$ npx @writhq/demo
Run the demo Read the spec records, not rails — no custody, no money transmission
  Chain of agency resolve any action → the accountable human
ACTION LIABILITY signs authorizes issued by bears event Action account.refill $500.00 agt_ Agent treasury-bot Ed25519 · claude-code mnd_ Mandate scoped · signed revocable prn_ Principal KYC verified US · accredited bearer Liability accountable party disputes · recourse One signed assertion. One round trip. A resolvable, co-signed record of who is accountable.
The gap

Your API can't tell who is behind an agent's request.

It looks like a person, but it isn't — and nothing on the wire says whose agent it is, what it may spend, or who is accountable if it's wrong. So the safe answer is to block it, and real volume goes away.

?

Human or agent?

Nothing on the wire distinguishes the two.

?

Whose agent?

An anonymous API key is not a counterparty.

?

What authority?

This action, this amount, this platform, right now?

?

Whose liability?

If it goes wrong, who do you dispute with?

How it works

Three moves. One round trip at the edge.

Setup happens once. After that, every agent call is one signed assertion and one verify call that fails closed.

STEP 01

A human onboards and issues a mandate

Pass KYC once, register the agent's public key, grant narrow authority: this action, these caps, this platform, expiring — revocable in seconds.

prn_ → mnd_ · signed grant
STEP 02

The agent signs every request

The SDK (or the MCP server in Claude Code) holds the keypair and signs each call — action, amount, platform, nonce, timestamp — in an X-Passport header.

agt_ key · JWS · nonce + ts
STEP 03

Your API verifies in one call

One middleware line calls /v1/verify: signature, mandate, scope, live revocation, caps, KYC. Back comes allow/deny, the resolved chain, and a signed receipt.

plt_ → vrf_ · allow / deny · co-signable
Sequence diagram of the full interaction: the principal onboards and issues a mandate once; on every call the agent signs an assertion, the platform verifies it with Writ in one round trip, then executes and countersigns on allow — or returns a 403 with a reason on deny.
Setup once; then one signed assertion and one verify round trip per call. Protocol →
Integrate

Accept agents in about ten lines.

Drop one middleware in front of the routes agents call. It fails closed — and anonymous callers get a 403 KYA required with an onboarding link instead of a silent block.

server.tsplatform · @writhq/verify
import { requireKYA } from "@writhq/verify";

// Accept agent traffic — one round trip, fails closed.
app.post(
  "/api/refill",
  requireKYA({ action: "account.refill" }), // ← the integration
  async (c) => {
    const { chain } = c.get("kya");   // resolved chain
    await accounts.refill(c.req.valid("json"));
    return c.json({ ok: true, principal: chain.principal });
  }
);
HTTP 403 · KYA required

No passport on this request.

The middleware never executes the action for an anonymous agent. It returns a structured block with a link to onboard — so the traffic you used to throw away comes back verified.

anonymous agent → 403 → onboard → mandate → verified buyer
The demo · one command

Watch an agent buy — and get stopped when it shouldn't.

treasury-bot holds one mandate at the Northbank sandbox: account.refill ≤ $1,000/tx, ≤ $2,500/week. Everything below is what npx @writhq/demo runs against production.

treasury-bot @ claude-code  ·  northbank.sandbox
$ passport present --action account.refill --amount 500
ALLOW$500.00 reason ok · receipt vrf_9c1… signed remaining this week $2,000.00
$ passport present --action account.refill --amount 2000
DENY$2,000.00 reason per_tx_cap · mandate cap $1,000 / tx
$ passport present --action account.refill --amount 900
ALLOW$900.00 reason ok remaining this week $1,100.00
$ passport present --action account.refill --amount 900
ALLOW$900.00 reason ok remaining this week $200.00
$ passport present --action account.refill --amount 900
DENY$900.00 reason period_cap · only $200.00 left this week
⟳ principal revokes mnd_tr7… in the permissions console — one click, propagates in < 2s
$ passport present --action account.refill --amount 500
DENY$500.00 reason mandate_revoked · live revocation check
$ curl -s -X POST northbank.sandbox/api/refill -d amount=250 # no X-Passport header
403KYA required onboard → northbank.sandbox/kya/start
 

Every ALLOW returns the resolved chain and a JWS receipt the platform can countersign — a record signed by both sides.

Mandates

The grant is scoped, signed, and revocable.

One principal, one agent, specific actions with hard caps, an expiry, and a live status. Platforms read the attributes they need — never the identity documents behind them.

mandate.jsonsigned grant
{
  "id": "mnd_tr7…",
  "principal": "prn_a1…",
  "agent": "agt_9f…",
  "scopes": [{
    "action": "account.refill",
    "max_amount_per_tx": 100000,
    "max_amount_per_period": 250000,
    "period": "week",
    "currency": "USD",
    "platforms": ["plt_northbank"],
    "purpose": "trading-capital"
  }],
  "not_before": "2026-07-22T00:00:00Z",
  "expires_at": "2026-08-22T00:00:00Z",
  "status": "active",
  "issuer_sig": "<JWS by passport issuance key>"
}
max_amount_per_tx · per_period

Hard caps

Per-transaction and per-period ceilings. The passport keeps the counters server-side.

not_before · expires_at

Time-boxed

Authority is valid only inside a window. Outside it, the passport denies first.

status: active → revoked

Instant revocation

One click in the permissions console. Verify is a live check, so the next request denies.

selective disclosure

Attributes, not documents

KYC level, country, accreditation flag — never the underlying documents.

Who pays

If you own the agent, Writ is free forever.

Onboarding a principal, registering an agent's key, and issuing or revoking mandates cost nothing — no cap, no trial, no plan to be on. Principals are never metered and never billed. Metering lives on the other side of the call: the platform pays for the verifications it makes, and its first 1,000 a month are free too.

agent owners → free forever  ·  platforms → 1,000 verifications/mo free
Signatures · document.sign

Your agent signed it. Was it authorized?

Agents already sign NDAs and order forms. Making the signature is the easy part. Proving the agent was allowed to make it is the hard part. Writ answers that in one call: this agent, this document, this much liability, this mandate, this KYC'd human.

the chain

signature ← agent ← mandate ← principal (KYC'd) ← liability

The counterparty resolves the whole chain before it executes the signature — and gets a signed record of the answer.

signing-mandate.jsonsigned grant
{
  "id": "mnd_ka9…",
  "principal": "prn_a1…",
  "agent": "agt_9f…",
  "scopes": [{
    "action": "document.sign",
    "document_classes": ["nda", "order_form"],
    "max_amount_per_tx": 5000000,      // $50,000 per document
    "max_amount_per_period": 20000000, // $200,000 a month
    "period": "month",
    "currency": "USD",
    "platforms": ["plt_counterparty"],
    "purpose": "vendor-paperwork"
  }],
  "expires_at": "2026-12-31T00:00:00Z",
  "status": "active",
  "issuer_sig": "<JWS by passport issuance key>"
}
document_classes

Which paper, at all

NDAs and order forms — nothing else. Any other class denies document_class.

max_amount_per_tx

One document, capped

$50,000 of liability on any single document. Over it denies per_tx_cap.

max_amount_per_period

Total exposure, capped

$200,000 a month across every document. Over it denies period_cap.

same caps · same counters

No second engine

The payment wedge meters money. This meters liability. Same code, same server-side counters, same deny reasons.

The boundary

We attest authority. We never produce the signature.

Writ says whether the agent was authorized to sign, and returns a signed record of that decision. Your e-signature platform still makes the signature. This is not a qualified electronic signature (eIDAS/QES) and does not try to be.

agent asks → Writ answers → you sign, or you don't

UETA §14 and ESIGN already say a contract can be formed by electronic agents, and agency law already says an agent binds its principal only within the authority it was granted. Writ is the record of that authority.

$ npx @writhq/demo sign

One command, no signup — an agent 403s anonymously, signs two documents inside its mandate, denies on class, both caps, replay and revocation. 13 checks against production.

Integrate as an e-signature platform How a counterparty verifies it

Where we sit
Category law

Records, not rails. We attest decisions; we never custody or move value.

Rails-neutral

x402 · AP2 · ACP interop. AP2 mandates map to ours 1:1 — the KYA layer any rail can call.

Regulatory posture

No custody, no money transmission. KYC through licensed vendors; attestation only.

Subject rights

FCRA-shaped by design. Access, correction, and dispute flows are first-class.

Standards path

W3C VC / JWS migration. Token format tracks the standard; migration is a serializer change.

Pricing

Sign yourself up. Free until the volume is real.

Three fields at api.writhq.com/signup — platform name, email, and password — get you a login and a plt_ id with an API key, shown exactly once. No invite, no waitlist, no admin token, no call. Plans meter verifications per calendar month, nothing else: every feature is on every plan. Allow and deny both count — a deny is the product working. This page is for platforms — the side that calls /v1/verify. Agent owners issuing mandates are never metered at all; see who pays.

FREE

$0 · 1,000 verifications a month

Mandates, caps, instant revocation, countersigning, audit export, signature authority — all of it. The free plan is the product, metered lower.

sign up → plt_ id + key in under a minute
GROWTH

$99/month · 100,000 verifications a month

Same surface, more room. Upgrade yourself from the platform dashboard — Stripe Checkout, no sales step, effective immediately.

self-serve upgrade · manage billing in-dashboard
ABOVE THAT

Talk to us

Past 100k verifications a month, or you need volume pricing or a regional deployment: [email protected].

volume pricing · regional deployment
terminalPOST /v1/auth/signup
curl -s -X POST https://api.writhq.com/v1/auth/signup \
  -H "content-type: application/json" \
  -d '{"platform_name":"My Platform","email":"[email protected]","password":"…"}'

# → 201 {"account":{"id":"acc_…"},"platform":{"id":"plt_…","api_key":"plt_sk_…","plan":"free"}}
#   the key is shown once — put it in your secret store now
#   the same email + password log you in at /login
HTTP 429 · quota_exceeded

A quota 429 is not a deny.

Over the monthly limit, /v1/verify returns 429 with code: "quota_exceeded" — and the body carries no decision field at all. It is a billing limit on your account, not a judgment about the agent: the assertion was never evaluated. Never log it as a deny, never show the caller a KYA failure, and never let it fall through to executing the action.

deny = the agent lacked authority  ·  429 = you are out of quota

Get a free key Plans & quotas in the docs

FAQ

Regulatory posture, privacy, and who goes first.

01 Are you a money transmitter or custodian?
No. Writ is an attestation layer. We sell decisions and records, never movement of value — we never touch, hold, or route funds. KYC runs through licensed vendors behind a pluggable interface.
02 What does a platform actually see about the human?
Attributes, not identity documents: KYC level, country, and an accreditation flag — enough to decide, nothing more. Full identity is disclosed only when the mandate grants it or a lawful request compels it. Subject access, correction, and dispute flows are built in (FCRA-shaped).
03 Who integrates first?
Anyone selling usage or credits to agents: API platforms and credit top-ups, ad-budget refills, trading and prop platforms, exchanges and on-ramps, marketplaces. The refill primitive generalizes wherever agents move value into a platform.
04 Do you compete with x402 / AP2 / ACP payment rails?
No — we're the identity layer any of them can call. A rail moves the value; the passport answers whether the agent moving it is authorized and who is accountable. AP2's mandates and ours are meant to map 1:1.
05 How do agents hold keys today?
The agent runtime holds an Ed25519 keypair and registers the public key with us. MVP mandate issuance is custodial — we sign after the principal authorizes in the dashboard (the CA model). The documented upgrade path is principal-held keys and W3C Verifiable Credentials with identical semantics.
06 What's shipped, and what's deliberately out?
Shipped and live: self-serve signup with a login and a key in hand in under a minute, accounts with email/password login, named API keys with independent revocation and last-used timestamps, the permissions console for registering agents and issuing/revoking mandates, the chain model, mandates with caps and revocation, the verify + countersign API, audit export, signature authority, the Northbank demo, the MCP server, and a CLI. KYC is a sandbox stub and the sanctions screen is a stub interface — deliberate, and the interface is shaped like the vendors that fill it. Documented but not built yet: real KYC vendors, principal-held keys/VCs, webhooks, multi-sig mandates, per-jurisdiction rulepacks, and agent reputation scores.
Live · self-serve

Run the whole chain in one command.

No signup for the demo itself — it creates a principal, an agent and a mandate, then walks allow → replay-block → cap-deny → live revocation → anonymous 403 against the live sandbox. When you want your own platform login and key, that is three fields and $0 — the free plan is 1,000 verifications a month with every feature on.

$ npx @writhq/demo

11 passed of 11 · the passport runs at api.writhq.com · curl the 403 yourself on the Launch page.