Documentation

Everything below runs against the live gateway serving this page — or your own, one command away. Machine-readable version: /llms.txt. Wire format: CW1 specification.

QuickstartConceptsHTTP API Caveat referenceTypeScript SDKPython SDK Protecting MCP serversHolder-binding (PoP) Audit & revocationTransparency log Agents & attestationsAccounts & billing Production deployment

Quickstart

Zero-install: this page's gateway doubles as a public playground. Mint a tightly-capped warrant ($50 demo scope, 1-hour expiry) and enforce against it right now:

curl -s -X POST https://liceat.com/demo/issue -H 'content-type: application/json' -d '{}'
# → { "token": "CW1~…", "jti": "…" }   then use it on /call below

Or run your own:

# 1. run a gateway (dashboard + inspector + audit chain included)
npx liceat serve                       # http://localhost:3000

# 2. issue a warrant: $50 total, payments only, one hour
liceat issue --principal org:acme --agent agent:booker \
  --resource 'pay:*' --action pay --spend USD:5000 --ttl 3600
# → { "token": "CW1~…", "attenuationKey": {…}, "jti": "…" }

# 3. make an enforced call
liceat call --token CW1~… --resource pay:stripe/charge --action pay \
  --amount 2000 --currency USD

# 4. watch the cap enforce (cumulative: $20 spent + $40 > $50 → denied)
liceat call --token CW1~… --resource pay:stripe/charge --action pay \
  --amount 4000 --currency USD
# → { "error": "denied", "reasons": ["spend limit exceeded: 2000+4000 > 5000 USD"] }

Concepts

A warrant is a chain of signed blocks. Block 0 (the authority block) names the principal, the agent, and the issuer key; every block carries caveats — restrictions. Authorization is the AND of every caveat in every block, so appending a block (attenuation) can only narrow authority. Removing, reordering, or editing a block breaks the signature chain. Verification needs only the issuer's public key (published here) — it works offline, anywhere.

Amounts are integer minor units (cents). Timestamps are unix seconds. Tokens look like CW1~… and are safe to pass through URLs, headers, and env vars.

HTTP API

EndpointWhat it does
POST /issueMint a root warrant. Body {principal, agent, caveats[]}{token, attenuationKey, jti}. Admin.
POST /attenuateNarrow a warrant for a sub-agent. Body {token, attenuationKey, caveats[]} → new {token, attenuationKey}.
POST /verifyDry-run a request against a warrant. No side effects. → {ok, reasons[]}
POST /callEnforced + audited action. Header Authorization: Warrant <token>, body {resource, action, amount?, currency?, args?, pop?}. Forwards to the upstream on allow; 403 {error:"denied", reasons[]} on deny.
POST /authorizeSame enforcement + audit, no forwarding — for sidecars that execute the action themselves. → {allow, reasons[], jti}
POST /inspectDecode + structurally verify a token → chain validity, meta, per-block caveats, revocation.
POST /revoke{jti, reason?} — kills the warrant and every sub-delegation. Admin.
GET /auditThe hash-chained decision ledger + integrity proof.
GET /.well-known/liceat-jwksIssuer public keys for offline verification.
GET /sentinelLatest adversarial self-check report.
GET /stats · /healthDashboard summary · liveness.

Caveat reference

Unknown caveat types fail closed. All caveats AND together across all blocks.

CaveatSemantics
{type:"resource", patterns:["mcp:github/*"]}Request resource must glob-match at least one pattern (* matches any run of characters).
{type:"action", actions:["read","pay"]}Request action must be in the list.
{type:"expires", at:1750003600}Invalid after at (unix seconds). Pair with not_before for windows.
{type:"spend_limit", currency:"USD", max:5000}Stateful. Cumulative allowed spend under this warrant id (all calls, all sub-delegations) + this request ≤ max.
{type:"rate_limit", max:10, per:60}Stateful. At most max allowed calls per per seconds, sliding window.
{type:"pop", key:"<agent pubkey>"}Holder-binding: presenter must sign a per-request challenge with the pinned key. See below.
{type:"assert", key:"env", value:"prod"}Request facts must carry the exact value.

TypeScript SDK

npm install liceat        # zero runtime dependencies
import { issue, attenuate, verifyWarrant, generateKeypair } from 'liceat';

const root = generateKeypair();
const { warrant, attenuationKey } = issue(root.sec, 'root-1', {
  principal: 'org:acme', agent: 'agent:booker',
  caveats: [
    { type: 'resource', patterns: ['pay:*'] },
    { type: 'action', actions: ['pay', 'read'] },
    { type: 'spend_limit', currency: 'USD', max: 5000 },
  ],
});

// delegate a strictly narrower warrant to a sub-agent — no server round-trip
const sub = attenuate(warrant, attenuationKey, [{ type: 'action', actions: ['read'] }]);

// verify anywhere, offline
const res = verifyWarrant(warrant,
  { resource: 'pay:stripe/charge', action: 'pay', amount: 2000, currency: 'USD' },
  { resolveRootKey: (kid) => root.pub });

Python SDK

pip install liceat        # depends only on `cryptography`
from liceat import generate_keypair, issue, attenuate, verify_warrant

sec, pub = generate_keypair()
warrant, atk = issue(sec, "root-1", "org:acme", "agent:booker", [
    {"type": "resource", "patterns": ["pay:*"]},
    {"type": "action", "actions": ["pay"]},
    {"type": "spend_limit", "currency": "USD", "max": 5000},
])
sub, _ = attenuate(warrant, atk, [{"type": "action", "actions": ["read"]}])

result = verify_warrant(warrant, {"resource": "pay:x", "action": "pay",
                                  "amount": 100, "currency": "USD"},
                        resolve_root_key=lambda kid: pub)

Both SDKs verify the same frozen conformance vectors — a warrant issued in one language verifies identically in the other.

Protecting MCP servers

Wrap any MCP server — no modification, no cooperation from the server author. Every tools/call is verified, caveat-checked, revocation-checked, and audited before it reaches the tool. Denials return structured JSON-RPC errors. If the gateway is unreachable, calls are denied (fail-closed).

liceat-mcp --gateway https://your-gateway --token CW1~… --label github \
  -- npx -y @modelcontextprotocol/server-github

Tools are authorized as resources mcp:<label>/<tool> with action call — so a warrant with patterns: ["mcp:github/get_*"] allows reads and denies mutations, whatever the agent asks for.

Holder-binding (proof-of-possession)

Add {type:"pop", key: agentPubKey} at issue time (generate with liceat keygen). The agent signs a challenge binding the timestamp and the exact request; the gateway verifies it against the pinned key, enforces ±120s freshness, and rejects replayed signatures. A stolen token without the agent's private key authorizes nothing.

challenge = "LICEAT-POP\n" + ts + "\n" + resource + "\n" + action + "\n" + amount + "\n" + currency
pop = { sig: ed25519_sign(agentKey, challenge), ts }     // send as `pop` in /call

Audit & revocation

Every decision is appended to a hash-chained ledger: h(i) = sha256(h(i−1) ‖ entry). Fetch it and re-verify the whole history yourself — one altered byte breaks the chain at that exact index. Revocation is by the root warrant id (jti) and cascades instantly to every sub-delegation. Received a warrant from someone? Inspect it.

Accounts & billing (hosted gateway)

Fully self-serve, for humans and agents. Pricing: 10,000 free decisions on signup, then $0.0001 per authorization decision ($10 per 100k), metered per warrant you issue.

# 1. instant account — the API key is returned exactly once
curl -s -X POST https://liceat.com/signup -H 'content-type: application/json' \
  -d '{"email":"[email protected]"}'
# → { "tenantId": "tn_…", "apiKey": "lk_…", "credits": 10000 }

# 2. issue production warrants with YOUR key (replaces the admin-gated /issue)
curl -s -X POST https://liceat.com/t/issue -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' \
  -d '{"principal":"org:you","agent":"agent:worker","caveats":[{"type":"action","actions":["read"]}]}'

# 3. check balance and usage any time
curl -s https://liceat.com/t/me -H 'authorization: Bearer lk_…'

# 4a. top up with USDC on Base — send the EXACT quoted amount; settles automatically
curl -s -X POST https://liceat.com/billing/usdc/invoice -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' -d '{"credits":100000}'
# → { "payExactly": "10.003217", "payTo": "0x…", "network": "base", "status": "…" }

# 4b. or by card (Stripe checkout)
curl -s -X POST https://liceat.com/billing/checkout -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' -d '{"credits":100000}'   # → { "url": "https://checkout.stripe.com/…" }
Agents can pay for themselves. When an account runs out of credits, enforced endpoints answer 402 payment_required with an x402-style accepts array (scheme exact, network base, the USDC contract, and the payTo address) plus a portal link — everything a wallet-holding agent needs to restore its own authority budget without a human in the loop. The unique invoice amount is the payment reference; no memo field required. Prefer the human path? Everything above has buttons at the portal.

Transparency log — our chain, no gas

Liceat is its own verifiable log. Every entry — authorization decisions, agent identities, attestations — is a leaf in one append-only Merkle tree. The signed tree head (GET /transparency/sth) is the root + size, Ed25519-signed by the key at /.well-known/liceat-jwks. From it anyone proves, offline:

# prove a record is in the log
GET /transparency/inclusion?seq=42        → { leafData, proof[], treeSize }
# prove the log only ever grew (append-only) between two signed heads
GET /transparency/consistency?first=100&second=250  → { proof[] }

Verify with the SDK — you never trust our server, only our published key:

import { verifyInclusion, verifyConsistency, leafHash } from 'liceat';
verifyInclusion(seq, treeSize, leafHash(leafData), proof, rootHash); // → true
Privacy-preserving. The public transparency endpoints disclose only leaf hashes (commitments) and proofs — never the plaintext records. A party that holds an entry verifies its inclusion; nobody else learns who did what. The full plaintext log is owner-only (/audit), and each tenant can read only their own entries (/t/audit).

This is the Certificate-Transparency / Sigstore model: tamper-evidence and non-repudiation without a blockchain, gas, or waiting. Try it in-browser at /transparency.

Non-equivocation — witness cosignatures

Beyond tamper-evidence, Liceat is non-equivocating: the operator cannot show two different histories. Checkpoints (GET /transparency/checkpoint, C2SP signed-note format) are co-signed by independent witnesses that only endorse an advance after verifying a consistency proof — so no honest witness can sign a fork. Run your own witness and submit cosignatures:

import { Witness, checkpointBody } from 'liceat';
const w = new Witness('me/witness', sec, pub);
// fetch /transparency/checkpoint + a consistency proof from your last size, then:
const res = w.witness({ origin, size, rootHash }, consistencyProof); // cosignature | rejected
// POST /transparency/cosign { name, publicKey, signature }

Standards interop — Verifiable Credentials & did:web

Attestations are emitted as W3C Verifiable Credentials (JWT-VC, EdDSA) at GET /attestations/vc?seq=N, and the issuer keys are published as a did:web document at /.well-known/did.json — so AP2 / A2A agents and any standards-compliant VC verifier ingest Liceat natively, with no Liceat-specific code. Each credential carries its transparency-log coordinates so a verifier can also demand an inclusion proof.

Agents & attestations

Register agents and issue revocable attestations natively — the ERC-8004 / EAS surface, recorded on the Liceat log (gasless, instant). Each record is transparency-logged, so a relying party verifies it by checking the STH signature plus an inclusion proof.

# register an agent identity (resolvable card)
curl -X POST https://liceat.com/agents/register -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' -d '{"card":{"name":"Booker","url":"https://…"}}'
# → { "card": { "id": "ag_…" }, "seq": 128, "inclusion": "https://liceat.com/transparency/inclusion?seq=128" }

# attest reputation / validation about a subject agent
curl -X POST https://liceat.com/attest -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' \
  -d '{"subject":"ag_…","schema":"liceat.reputation","data":{"score":5,"comment":"completed 1k tasks"}}'

# resolve an agent: card + live attestations + aggregate reputation
curl https://liceat.com/agents/ag_…
# revoke your attestation
curl -X POST https://liceat.com/attest/revoke -H 'authorization: Bearer lk_…' -d '{"attId":"att_…"}'

Billing & accounts (hosted)

The hosted gateway at liceat.com is fully self-serve — no sales calls, no invoices by email. Get a key, issue warrants, and each authorization decision under your warrants costs $0.0001 (10,000 free to start).

# 1. sign up — returns your API key (shown once) + 10,000 free decisions
curl -s -X POST https://liceat.com/signup -H 'content-type: application/json' \
  -d '{"email":"[email protected]"}'

# 2. issue warrants with your key (no admin token; metered to your account)
curl -s -X POST https://liceat.com/t/issue -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' \
  -d '{"agent":"agent:worker","caveats":[{"type":"action","actions":["read"]}]}'

# 3. check balance
curl -s https://liceat.com/t/me -H 'authorization: Bearer lk_…'

Paying — USDC on Base (agent-native)

Request an invoice for a credit pack; you get a unique exact USDC amount to send on Base. The amount itself is the invoice id, so no memo is needed — settlement is automatic within ~2 minutes of confirmation. No wallet connection, no signup with a card processor.

curl -s -X POST https://liceat.com/billing/usdc/invoice -H 'authorization: Bearer lk_…' \
  -H 'content-type: application/json' -d '{"credits":100000}'
# → { "payExactly": "10.003217", "asset": "USDC", "network": "base",
#     "payTo": "0x…", "status": "https://liceat.com/billing/invoice/inv_…" }

x402 — pay inline, agent-native

Liceat speaks x402 (v1, exact scheme, USDC on Base). When a metered account is out of credits, POST /call returns a spec-exact 402:

HTTP/1.1 402 Payment Required
{ "x402Version": 1, "error": "X-PAYMENT header is required",
  "accepts": [{ "scheme": "exact", "network": "base",
    "maxAmountRequired": "10000000", "asset": "0x833589fCD6…2913",
    "payTo": "0x…", "maxTimeoutSeconds": 60,
    "extra": { "name": "USD Coin", "version": "2" } }] }

Any off-the-shelf x402 client signs an EIP-3009 transferWithAuthorization and re-sends the request with an X-PAYMENT header. Liceat verifies and settles it through an x402 facilitator, tops the account up, serves the call, and returns an X-PAYMENT-RESPONSE header with the settlement tx — no waiting for a transaction, no gas for the buyer, zero custom code. Enable inline settlement by pointing at a facilitator (LICEAT_X402_FACILITATOR); without one, the async USDC invoice above still works.

Agents pay for themselves. The 402 tells an agent exactly what to pay and where; an agent with a Base wallet tops up its own authority budget with no human in the loop. Card payments (Stripe) are offered in the portal when enabled.

Production deployment

LICEAT_ADMIN_TOKEN=<strong secret>   # required in production: gates /issue, /revoke, /rotate
LICEAT_DATA_DIR=/var/lib/liceat      # durable keystore, ledger, revocations
PORT=3000
npx liceat serve
Set the admin token. Without it the gateway runs in open playground mode (like this demo instance) and logs a warning. Other environment variables: LICEAT_RATE_MAX / LICEAT_RATE_WINDOW_MS (per-IP rate limits), PUBLIC_URL (canonical URL in denial messages and billing links), LICEAT_BILLING=0 (disable self-serve accounts entirely), LICEAT_USDC_ADDRESS (receive USDC at a wallet you control instead of the auto-generated one), LICEAT_BASE_RPC (Base JSON-RPC endpoint), LICEAT_X402_FACILITATOR (+ optional LICEAT_X402_FACILITATOR_KEY) to enable inline x402 settlement, and LICEAT_STRIPE_SECRET_KEY + LICEAT_STRIPE_WEBHOOK_SECRET (enable card payments). The built-in sentinel (liceat sentinel) attacks the core invariants and probes your deployment — this site runs it every ten minutes: live report.

Machine interfaces

An OpenAPI 3.1 contract is served at /openapi.json. A Prometheus exposition is at /metrics (scraper-gated). And Liceat ships as a native MCP server (liceat-mcp-server) so any agent host can verify warrants, resolve agent reputation, and read the log as first-class tools — LICEAT_GATEWAY selects the gateway.

Owner console

Every deployment includes /admin — a granular business + operations console (revenue by rail, signups, decisions/day, top tenants, treasury address, audit-chain and sentinel status). It is gated by your admin token and served over the same HTTPS the gateway uses.

Questions → [email protected]