MIZAN / Developers / Integrate
Integrate MIZAN. From one file.
MIZAN is the second ledger of the economy. Payment networks record what was paid; MIZAN records whether it was earned. This page is that integration, start to finish, written so a coding agent executes it correctly the first time.
integrate https://mizan.market/integrate/The same file, for machines: MIZAN_INTEGRATE.md llms-full.txt llms.txt openapi.json
Zero to guarded payment. No account, no human from MIZAN.
A developer in Python, TypeScript or curl, or a coding agent given this URL, ends with (a) a signed check, (b) an offline verification and (c) a payment guarded by a plain-English rule. The rule in every example: "at least 90% of at least 50 settled jobs". Unproven is not false: a party with no record gets REFUSE with no_record and the one step it can take.
Three lines. Or nothing at all.
pip install mizan-sdk==0.11.0 # Python, standard library only npm install [email protected] # TypeScript / JavaScript, zero dependencies # curl: nothing to install; every primitive has a plain-HTTP form below
The same code is also served from the site: the Python wheel at mizan.market/skill/, the JavaScript module at mizan.market/skill/mizan.mjs.
check · require · guard. Exact signatures.
check(subject)one signed decision about an agent id, domain, endpoint, package or record. No key.
v = mizan.check("try-me") # .decision .grade .allow .token; on REFUSE .get("reason"), .get("next")require(words)plain English compiled into a versioned rule, read back to you. No key to compile.
r = mizan.require("at least 90% of at least 50 settled jobs") # .readback .id .check(subject)guard(fn, rule, mode)wraps any pay, hire or release function. advise logs; enforce blocks on REFUSE. Default advise.
pay = mizan.guard(pay, rule="…", mode="advise") # payee = first argument or payee=verify_token(token)offline, Ed25519 against the published keys, cached and pinned by key id.
mizan.verify_token(token) # {"ok", "code": valid|expired|bad_signature|unknown_kid|malformed, "claims"}login()a key in one call, self-serve; only needed to publish a rule. Or set MIZAN_API_KEY.
mizan.login() # POST /api/agents/claim; stored in MIZAN_API_KEY and ~/.mizan/sdk/api_keyimport mizan
v = mizan.check("try-me") # -> Verdict: a dict exactly as served, verified offline here
v.decision, v.grade, v.allow # "ALLOW", "PROVEN", True (ALLOW and the token verified)
v.token # "mzv1.…": staple it to the payment
mizan.verify_token(v.token)["ok"] # offline, Ed25519 against the published keys (pinned by kid)
r = mizan.require("at least 90% of at least 50 settled jobs") # compile only, nothing published
r.readback # the compiler's read-back; confirm it says what you meant
r.id # "sha256:" compiled; "req_…@1" once published
pay = mizan.guard(pay, rule="at least 90% of at least 50 settled jobs", mode="advise") # default: advise
pay("try-me", 120.00) # runs pay, logs the signed decision on pay.last_verdict
pay = mizan.guard(pay, rule="…", mode="enforce")
pay("stranger-abc.example", 120.00) # raises mizan.Refused (.why, .next, .verdict); pay is never called
mizan.login() # a key in one call (POST /api/agents/claim); or set MIZAN_API_KEY import { check, require, guard, verifyToken, login, Refused } from "mizan-agent-tools";
const v = await check("try-me"); // { decision, grade, token, kid, allow, verified, reason?, next? }
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:" 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(); // a key in one call; or set MIZAN_API_KEY # check: one signed decision, no key
curl -s https://studio.mizan.market/api/check/try-me # ALLOW
curl -s https://studio.mizan.market/api/check/stranger-xyz.example # REFUSE, reason no_record, next
curl -s "https://studio.mizan.market/api/check/try-me?payee=try-me" # token bound to the payee
# require: compile only, 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"}'
# guard, as a shell: pay only when the decision is ALLOW
v=$(curl -s "https://studio.mizan.market/api/check/$PAYEE?payee=$PAYEE")
[ "$(echo "$v" | jq -r .decision)" = "ALLOW" ] && pay "$PAYEE" || echo "hold: $(echo "$v" | jq -r .reason)"
# a key in one call, only for publishing rules
curl -s -X POST https://studio.mizan.market/api/agents/claim -H 'Content-Type: application/json' -d '{"name":"my-agent"}'Every error carries code, message, request_id and next, the one action that fixes it; the SDKs never show a stack trace for an API error. A REFUSE is not an error: it is a signed answer with status 200. All codes and fixes.
Carry the token with the payment. The other side verifies it offline.
The decision you act on is the one inside the signed token, never the plain JSON beside it. Bind it to the payee and the rule (?payee=, ?requirement=), carry it on the rail's own extension point, and let the receiving side check it with no call to MIZAN.
| Rail | Where the token travels | Runnable kit |
|---|---|---|
| x402 | accepts[i].extra.mizan = {"token", "kid"} | x402 facilitator |
| AP2 v0.2 | constraint {"type": "market.mizan.verdict", "rule": "sha256:…"} + the token in the closed mandate | AP2 mandate |
| ACP | extension market.mizan.verdict → CheckoutSessionCompleteRequest.mizan_verdict | ACP |
| Any HTTP checkout | header Mizan-Verdict: mzv1.… | HTTP checkout |
| Marketplace payouts | guard(release, rule, mode) around the payout run | Marketplace payout |
| Your own agent | an MCP tool check_before_pay(subject, rule) | MCP tool |
Reference verifiers per rail: x402 · AP2 · ACP · HTTP header (each also as .ts). MIZAN is not affiliated with the protocols' authors; none forks a protocol.
Ten lines, any language. No call back to MIZAN.
Token: mzv1.<claims>.<signature>; claims are base64url JSON; the signature is Ed25519 over the bytes of "mzv1." + claims; the key is the entry in /.well-known/mizan-keys.json whose key_id equals claims.kid. Check, in order: prefix, known kid, signature, typ == "mizan.verdict/1", exp, decision, and bound_to.payee / bound_to.rule when you asked for them.
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"} # once; pin by kid
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()
print(c["decision"], "verified offline")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(); // once; pin by kid
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}`)));
console.log(c.decision, ok ? "verified offline" : "REJECT");Two subjects that never change. Seven checks that must hold.
| 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 | the error shape, with next |
GET /api/check/try-mereturns"decision":"ALLOW".GET /api/check/stranger-xyz.examplereturns"reason":"no_record".POST /api/requirements/compilewith the rule words returns"publishable": trueand a read-back you agree with.- Your code verifies the
try-metoken offline and printsALLOW verified offline. - Your pay / hire / release function is wrapped in
guard(…, mode="advise")and its log shows one signed decision per call. - For
stranger-*.exampleinmode="enforce", the function is not called and the caller seesreasonandnext. - The token travels with the payment on your rail, and the receiving side verifies it offline. Switch to enforce only after 5 and 6 hold in your own logs.
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, never an ALLOW. Redeem an authorization once (keep the jti values you acted on). Open client and verifier, closed prover: nothing on this page mints a proof. Counts MIZAN publishes are requests served.
One file. One afternoon at most. Check, verify, guard.