qed-proof
Copyright 2026 Nuraveda Lab
Python SDK for QED Proof: submit a claim about work your agent did, get back an independently-verified receipt, and check that receipt locally — with no further call to QED Proof beyond fetching public keys (and, optionally, a public chain RPC for the on-chain anchor).
A receipt verifies identically here, in the TypeScript SDK, and in the spec's own reference
check.py — this package's verify_receipt is a line-for-line port of that tool, proven against
the same 20-vector conformance suite.
Install
pip install qed-proof
# or, if you'll check an on-chain anchor:
pip install "qed-proof[anchor]"
Requires Python 3.11+.
Quickstart
from qed_proof import QedProof, actions
qp = QedProof(api_key="qed_sk_...") # or set QED_PROOF_API_KEY
claim = qp.submit_claim(
actions.github_commit_push(target="owner/repo", sha="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", branch="main"),
agent_id="my-agent",
client_claim_id="deploy-42", # optional: resubmitting the same id returns the same claim (created=False)
)
result = qp.wait_for_verdict(claim.claim_id, timeout=120)
print(result.verdict) # "verified", "late", "mismatch", "failed" or "unverifiable"
receipt = qp.get_receipt(result.receipt_id) # no API key needed — receipts are public
List claims
page = qp.list_claims(limit=50, agent_id="my-agent") # newest first
for claim in page.claims:
print(claim.claim_id, claim.state, claim.verdict)
page.next_cursor # str, or None if this was the last page
# or walk every matching claim, following next_cursor automatically:
for claim in qp.iter_claims(agent_id="my-agent", action="github.commit.push"):
print(claim.claim_id, claim.verdict)
limit (1-200, default 50), cursor, agent_id, action, verdict and state are all optional
filters; list_claims raises ValueError locally if limit is out of range, before making a
request. iter_claims takes the same filters plus page_size (default 50).
Verify a receipt
A receipt is meant to be checked by anyone, offline, without trusting QED Proof at request time — only its published Ed25519 keys.
from qed_proof import verify_receipt
keys = qp.get_keys() # GET /.well-known/poaw-keys.json — no API key needed
report = verify_receipt(receipt, keys)
report.valid # bool — every applicable check passed
report.verdict # the verdict, or None if invalid
report.achieved_trust_level # 0, 1 or 2 (2 needs inclusion + an on-chain anchor check)
report.checks # dict: spec_version, schema, integers_only, key, signature,
# claim_digest, inclusion, anchor — each True/False or a string
# reason ("absent", "not_checked_offline", or a failure code)
report.to_dict() # the same shape as the reference tool's JSON report
Or in one call, using the client's own key fetch:
report = qp.verify(receipt)
Checking the on-chain anchor
Some receipts include a Merkle inclusion proof anchored to an EAS attestation on Base. Checking that
against the chain needs the anchor extra and a JSON-RPC URL:
report = verify_receipt(receipt, keys, rpc_url="https://sepolia.base.org")
Without rpc_url, an anchored receipt's checks["anchor"] reads "not_checked_offline" rather than
True — it's neither proven nor disproven. Any RPC error, decode error or mismatch fails the check
(checks["anchor"] becomes a reason string, e.g. "root_mismatch") — it is never treated as a pass.
Calling verify_receipt(..., rpc_url=...) without the anchor extra installed raises a clear
ImportError telling you to pip install "qed-proof[anchor]".
Self-hosted / a different node
Point the client at your own deployment instead of the default https://api.qedproof.site:
qp = QedProof(api_key="...", base_url="https://poaw.internal.example.com")
Actions
Typed helpers build the (action, target, params) triple for the live verifier profiles, validating
what's cheap to check locally (a receipt's actual verdict is always decided by the destination, never
by the SDK):
from qed_proof import actions
actions.github_commit_push(target="owner/repo", sha="<40 hex>", branch="main")
actions.github_pr_open(target="owner/repo", number=42, base="main", head_sha="<40 hex>")
actions.github_checks_pass(target="owner/repo", sha="<40 hex>")
actions.x_post_publish(target="@handle", post_id="1234567890", text_sha256="<64 hex>")
actions.slack_message_post(target="slack://T0123456789/C0123456789", ts="1234567890.123456")
actions.http_url_status(target="https://example.com", status=200, content_fingerprint="sha256:...")
Pass the result straight to submit_claim, or pass action (a string) with target= / params=
directly for an action this SDK doesn't yet have a helper for.
Async
AsyncQedProof mirrors the same surface over httpx.AsyncClient:
from qed_proof import AsyncQedProof
async with AsyncQedProof(api_key="...") as qp:
claim = await qp.submit_claim(..., agent_id="my-agent")
result = await qp.wait_for_verdict(claim.claim_id)
page = await qp.list_claims(agent_id="my-agent")
async for claim in qp.iter_claims(agent_id="my-agent"):
print(claim.claim_id)
Errors
QedProofError(status_code, detail)— any non-2xx response;detailis the API's own message. The API key is never included in an exception's message,repr, or the client's ownrepr.QedProofRateLimited— a 429, subclassingQedProofError, with.retry_after(seconds, from theRetry-Afterheader, orNone).wait_for_verdicthandles this for you: it backs off byretry_afterand keeps polling rather than raising.QedProofTimeout—wait_for_verdictdidn't see a decided claim withintimeoutseconds.
Links
- Docs: https://docs.qedproof.site
- License: Apache-2.0 (see
LICENSE)
Release files for qed-proof 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qed_proof-0.1.1.tar.gz | 30.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qed_proof-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.5 kB
Release files / qed_proof-0.1.1.tar.gz
| Download URL | qed_proof-0.1.1.tar.gz |
|---|---|
| Size | 30.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
223f27d3f26a77aa524d4a235bc3cddd4cf650e82618f5e462e8a1de50718044
|
|
BLAKE2b-256 checksum How to use checksums |
34ec0ad9339d29bfc2d4f93d6011a0ffbea597efcf7126aee5758e2e4c93e255
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / qed_proof-0.1.1-py3-none-any.whl
| Download URL | qed_proof-0.1.1-py3-none-any.whl |
|---|---|
| Size | 22.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
937fa1c32d687c4ec10b843e98963a658334a9e67c22ee332375824273d4ce4e
|
|
BLAKE2b-256 checksum How to use checksums |
59594ab2bf67538f95c607792faad582be9fe39101c7a98922545ca4d608c4b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log