# MIZAN_INTEGRATE.md — integrate MIZAN end to end from this one file

MIZAN is the second ledger of the economy. Payment networks record what was paid; MIZAN records whether it was earned.

This file is written for a coding agent (Claude Code, Cursor, Codex) or a developer. Read it once, then do the steps in order. No account is needed for steps 1–3. Nothing here mints a proof or moves money; the prover is closed, the client and verifier are open.

Canonical copies: https://mizan.market/MIZAN_INTEGRATE.md · https://mizan.market/llms-full.txt · human page https://mizan.market/integrate/
API base: `https://studio.mizan.market` · Keys: `https://mizan.market/.well-known/mizan-keys.json` (same set at `https://studio.mizan.market/.well-known/mizan-keys.json`)

## 0. What you are integrating (30 seconds)

One question before money, work or authority moves: *has this party earned it, by my rule?*
MIZAN answers `ALLOW` or `REFUSE`, signed (Ed25519). The answer travels as a compact token `mzv1.<claims>.<signature>` that anyone verifies offline against the published key. Unproven is not false: a subject with no record gets `REFUSE` with `reason: "no_record"` and a `next` step it can take.

Three primitives:

| primitive | what it does | key needed |
|---|---|---|
| `check(subject)` | one signed decision about an agent id, domain, endpoint URL, package or record | no |
| `require(words)` | compiles a plain-English rule into a versioned rule object, read back to you | no to compile; yes to publish |
| `guard(fn, rule, mode)` | wraps your pay / hire / release function: `advise` logs the decision, `enforce` blocks on REFUSE | no (`advise`), yes to publish the rule it enforces |

Plus `verify_token(token)` (offline) and `login()` (self-serve key, one call, no form).

Default rule to use in every example: **"at least 90% of at least 50 settled jobs"**.

## 1. Install

```bash
# Python 3.9+ (standard library only)
pip install mizan-sdk==0.11.0

# TypeScript / JavaScript (Node 20+, zero dependencies)
npm install mizan-agent-tools@0.3.0

# curl: nothing to install
```

If a registry install is not available yet, the same code is served from the site: Python wheel at https://mizan.market/skill/ (`pip install https://mizan.market/skill/mizan_sdk-0.11.0-py3-none-any.whl`), JavaScript module at https://mizan.market/skill/mizan.mjs. Every primitive below also has a plain-HTTP form (section 4), which needs nothing installed.

## 2. The three primitives: exact signatures

### Python (`import mizan`)

```python
import mizan

v = mizan.check("try-me")                     # -> Verdict (a dict, exactly as served, plus offline verification)
v.decision        # "ALLOW" | "REFUSE"
v.grade           # "PROVEN" | "RECORDED" | "DECLARED" | None
v.allow           # True only when MIZAN said ALLOW AND the token verified here, offline
v.token           # "mzv1.…"  staple it to the payment
v.why             # one line: what was met, or why not
v.get("reason")   # on REFUSE: "no_record" | "below_bar" | "evidence_against" | "unverifiable"
v.next            # on REFUSE: the one step the counterparty can take (a forwardable proof request)
v.verify()        # re-verify any time, offline

mizan.verify_token(v.token)                   # -> {"ok": bool, "code": "valid"|"expired"|"bad_signature"|"unknown_kid"|"malformed", "claims": {...}}
                                              #    offline: keys pinned by key id, fetched once, cached

r = mizan.require("at least 90% of at least 50 settled jobs")   # -> Requirement (compile only, nothing published)
r.readback        # the compiler's plain-English read-back; confirm it says what you meant
r.id              # "sha256:<hash>" of the compiled rule; "req_…@1" once published
d = r.check("try-me")                         # -> Decision: d.allow, d.grade, d.explanation, d.verified

pay = mizan.guard(pay, rule="at least 90% of at least 50 settled jobs", mode="advise")   # default mode: advise
pay("try-me", 120.00)                         # advise: runs pay, logs the signed decision on pay.last_verdict
pay = mizan.guard(pay, rule="…", mode="enforce")
pay("stranger-abc.example", 120.00)           # enforce: raises mizan.Refused (.why, .next, .verdict) and never calls pay
pay.last_verdict                             # the signed decision of the last call (advise or enforce)

mizan.login()                                 # self-serve key: POST /api/agents/claim; stored in MIZAN_API_KEY + ~/.mizan/sdk/api_key
mizan.login(api_key="mzn_…")                  # or set MIZAN_API_KEY in the environment
```

`guard` takes the payee from the wrapped function's first positional argument, or from a `payee=` keyword argument. Checks are free and anonymous; a key only identifies *you* (your rules, your record).

### TypeScript / JavaScript (`import { check, require, guard, verifyToken, login, Refused } from "mizan-agent-tools"`)

```ts
import { check, require, guard, verifyToken, login, Refused } from "mizan-agent-tools";

const v = await check("try-me");             // { decision, grade, token, kid, reason?, next?, allow, verified }
const t = await verifyToken(v.token);        // { ok, reason, header, payload }  offline, pinned keys

const r = await require("at least 90% of at least 50 settled jobs");   // compile only
r.readback; r.id;                                   // id: "sha256:<hash>" compiled, "req_…@1" once published
const d = await r.check("try-me");                 // { allow, grade, explanation, verified }

pay = guard(pay, { rule: "at least 90% of at least 50 settled jobs", mode: "advise" });   // default advise
pay = guard(pay, { rule: "…", mode: "enforce" });   // throws Refused on REFUSE; pay is never called

await login();                                // self-serve key; or set MIZAN_API_KEY
```

### Errors (both SDKs)

Every error object carries `code`, `message`, `request_id` and `next`: the one action that fixes it. The SDKs never surface a stack trace to the user for an API error. A `REFUSE` is **not** an error: it is a signed answer with HTTP 200.

```json
{"error": {"code": "invalid_request", "field": "amount",
           "message": "amount 'ten' is not a plain positive decimal …",
           "next": "send the amount as digits, e.g. amount=250 or amount=250.75",
           "request_id": "req_…"}}
```

## 3. Guard a payment on your rail

The decision you act on is the one **inside the signed token**, never the plain JSON beside it. On every rail the pattern is the same: check the payee, carry the token with the payment, the receiving side verifies it offline.

| rail | where the token travels | reference |
|---|---|---|
| x402 | `accepts[i].extra.mizan = {"token": "mzv1.…", "kid": "…"}` | https://mizan.market/verdict/x402-facilitator.py · `.ts` |
| AP2 v0.2 | Payment Mandate constraint `{"type": "market.mizan.verdict", "rule": "sha256:…"}` + the token in the closed mandate | https://mizan.market/verdict/ap2-verdict.py · `.ts` · schema `ap2-constraint.schema.json` |
| ACP | extension `market.mizan.verdict` → `CheckoutSessionCompleteRequest.mizan_verdict = {"token", "kid"}` | https://mizan.market/verdict/acp-verdict.py · `.ts` · schema `acp-extension.schema.json` |
| any HTTP checkout | request header `Mizan-Verdict: mzv1.…` | https://mizan.market/verdict/http-header-verdict.py · `.ts` |

Payer side, any rail (Python):

```python
import mizan
v = mizan.check(payee, payee=payee, rule=RULE_REF)   # bind the token to this payee and this rule
if not v.allow:
    raise SystemExit(f"hold: {v.get('reason')} · next: {v.get('next')}")
send_payment(payee, amount, extra=v.as_x402_extra())  # x402: {"mizan": {"token", "kid"}}; other rails: see table
```

Payee / facilitator side, offline:

```python
r = mizan.verify_token(token)                       # no call to MIZAN
ok = r["ok"] and r["claims"]["decision"] == "ALLOW" \
     and r["claims"].get("bound_to", {}).get("payee") == payee \
     and r["claims"].get("bound_to", {}).get("rule") == RULE_HASH
```

Runnable folders, one per surface, each with a 3-line README and a test: https://mizan.market/kits/ (x402 facilitator, AP2 mandate, ACP, HTTP checkout, marketplace payout, MCP tool).

## 4. The same calls over plain HTTP (what the SDKs do)

```bash
# check: one signed decision (no key)
curl -s https://studio.mizan.market/api/check/try-me
curl -s "https://studio.mizan.market/api/check/try-me?payee=try-me"          # token bound to a payee
curl -s "https://studio.mizan.market/api/check/try-me?requirement=req_…@1"   # against a published rule

# require, compile only (no key, nothing written)
curl -s -X POST https://studio.mizan.market/api/requirements/compile \
  -H 'Content-Type: application/json' -d '{"text":"at least 90% of at least 50 settled jobs"}'
# -> {"requirement": {...}, "shown_back": "Pay only if ALL of: …", "hash": "…", "unmatched": [], "publishable": true, "publish": {...}}

# publish the rule (key): read shown_back first, then confirm
curl -s -X POST https://studio.mizan.market/api/requirements -H "Authorization: Bearer $MIZAN_API_KEY" \
  -H 'Content-Type: application/json' -d '{"text":"at least 90% of at least 50 settled jobs","confirm":true}'
# -> {"ref": "req_…@1", "hash": "…"}

# a key, one call, no account
curl -s -X POST https://studio.mizan.market/api/agents/claim -H 'Content-Type: application/json' -d '{"name":"my-agent"}'
# -> {"agent_id": "ag_…", "api_key": "mzn_…", "shown_once": true}
```

MCP: `claude mcp add --transport http mizan https://studio.mizan.market/mcp` (tools `check`, `prove`, `more`). A2A card: https://studio.mizan.market/.well-known/agent-card.json. OpenAPI: https://mizan.market/skill/openapi.json.

## 5. Verify offline (any language, ~10 lines)

Token: `mzv1.<claims>.<signature>`; claims = base64url JSON; signature = Ed25519 over the ASCII bytes of `"mzv1." + <claims>`; the key is `keys[].public_key_b64` whose `key_id == claims.kid`, from `https://mizan.market/.well-known/mizan-keys.json` (fetch once, pin by `kid`).

Checks, in order: 3 parts and prefix `mzv1` → `claims.kid` is in the key set → signature verifies → `claims.typ == "mizan.verdict/1"` → `claims.exp > now` → `claims.decision == "ALLOW"` → if you asked for a rule or payee, `claims.bound_to.rule` / `claims.bound_to.payee` match.

```python
import base64, json, time, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey   # or the SDK's pure-Python fallback
b64u = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
keys = {k["key_id"]: k["public_key_b64"] for k in json.load(urllib.request.urlopen("https://mizan.market/.well-known/mizan-keys.json"))["keys"] if k.get("alg") == "Ed25519"}
pre, body, sig = token.split(".")
c = json.loads(b64u(body))
Ed25519PublicKey.from_public_bytes(base64.b64decode(keys[c["kid"]])).verify(b64u(sig), f"{pre}.{body}".encode())  # raises if forged
assert pre == "mzv1" and c["typ"] == "mizan.verdict/1" and c["exp"] > time.time()
```

```js
const b64u = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (ch) => ch.charCodeAt(0));
const keys = await (await fetch("https://mizan.market/.well-known/mizan-keys.json")).json();
const [pre, body, sig] = token.split(".");
const c = JSON.parse(new TextDecoder().decode(b64u(body)));
const k = keys.keys.find((x) => x.alg === "Ed25519" && x.key_id === c.kid);
const pub = await crypto.subtle.importKey("raw", b64u(k.public_key_b64), { name: "Ed25519" }, false, ["verify"]);
const ok = pre === "mzv1" && c.typ === "mizan.verdict/1" && c.exp > Date.now() / 1000 &&
  (await crypto.subtle.verify({ name: "Ed25519" }, pub, b64u(sig), new TextEncoder().encode(`${pre}.${body}`)));
```

## 6. Test subjects (always available, no key)

| subject | answer | use it for |
|---|---|---|
| `try-me` | `ALLOW`, grade `PROVEN` | the happy path; `?payee=try-me` binds the token |
| `stranger-<anything>.example` | `REFUSE`, reason `no_record` | the hold path; `next` carries a proof request |
| `try-me?amount=ten` | HTTP 400, `invalid_request` with `next` | the error shape |

Never use a real counterparty as a test subject. Never publish a rule you have not read back.

## 7. Done when (run these; every line must hold)

```bash
curl -s https://studio.mizan.market/api/check/try-me | grep -q '"decision":"ALLOW"'                      && echo 1 ok
curl -s https://studio.mizan.market/api/check/stranger-xyz.example | grep -q '"reason":"no_record"'       && echo 2 ok
curl -s -X POST https://studio.mizan.market/api/requirements/compile -H 'Content-Type: application/json' \
  -d '{"text":"at least 90% of at least 50 settled jobs"}' | grep -q '"publishable": true'                 && echo 3 ok
```

4. Your code verifies the `try-me` token offline (section 5) and prints `ALLOW verified offline`.
5. Your pay / hire / release function is wrapped in `guard(..., mode="advise")`, and its log shows one signed decision per call.
6. For `stranger-*.example` in `mode="enforce"`, the wrapped function is not called and the caller sees `reason` and `next`.
7. The token (or its `kid` + token pair) travels with the payment on your rail, and the receiving side verifies it offline.

Switch to `mode="enforce"` only after 5 and 6 hold in your own logs.

## 8. Rules of the road

- A decision is about the record, never advice about the payment; the relying party keeps responsibility.
- Fail closed: an unreachable MIZAN is a `REFUSE` with an `error` member, not an `ALLOW`.
- Check the token's claims, not the JSON next to it. Pin keys by `kid`; refresh the key set when you meet an unknown `kid`.
- Redeem an authorization once: keep the `jti` values you have acted on.
- Counts MIZAN publishes are requests served, not users. Nothing here claims adoption.
- Open client and verifier, closed prover: no call in this file mints a proof.

Reference: verdict format https://mizan.market/verdict/ · codes and fixes https://mizan.market/verdict/codes/ · status https://mizan.market/status/ · changelog https://mizan.market/changelog/ · the short index https://mizan.market/llms.txt
