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.

Paste this to your coding agentintegrate https://mizan.market/integrate/

The same file, for machines: MIZAN_INTEGRATE.md llms-full.txt llms.txt openapi.json

00 The 5-minute test

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.

01 Install

Three lines. Or nothing at all.

installPython 3.9+ · Node 20+ · curl
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.

02 The primitives

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_key
import 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
pip install mizan-sdk==0.11.0 · standard library onlytested against the live API

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.

03 Guard a payment on your rail

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.

RailWhere the token travelsRunnable kit
x402accepts[i].extra.mizan = {"token", "kid"}x402 facilitator
AP2 v0.2constraint {"type": "market.mizan.verdict", "rule": "sha256:…"} + the token in the closed mandateAP2 mandate
ACPextension market.mizan.verdict → CheckoutSessionCompleteRequest.mizan_verdictACP
Any HTTP checkoutheader Mizan-Verdict: mzv1.…HTTP checkout
Marketplace payoutsguard(release, rule, mode) around the payout runMarketplace payout
Your own agentan 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.

04 Verify offline

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")
cryptography, or the SDK's pure-Python fallbacktested against the live API
05 Test subjects and done-when

Two subjects that never change. Seven checks that must hold.

SubjectAnswerUse it for
try-meALLOW, grade PROVENthe happy path; ?payee=try-me binds the token
stranger-<anything>.exampleREFUSE, reason no_recordthe hold path; next carries a proof request
try-me?amount=tenHTTP 400, invalid_requestthe error shape, with next
  1. GET /api/check/try-me returns "decision":"ALLOW".
  2. GET /api/check/stranger-xyz.example returns "reason":"no_record".
  3. POST /api/requirements/compile with the rule words returns "publishable": true and a read-back you agree with.
  4. Your code verifies the try-me token offline 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 function is not called and the caller sees reason and next.
  7. 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.