Skip to main content

The official Python SDK for QuickContract — the new standard for contracts. For humans and AI agents.

Project description

quickcontract

The official Python SDK for QuickContract — the new standard for contracts. For humans and AI agents.

Wraps the QuickContract partner API + the agent-layer surfaces (mandate self-discovery, machine-readable terms, signed event ingress, anonymous hash verification) so Python-first agent frameworks (Anthropic Agents SDK, OpenAI Agents SDK, LangChain, plain scripts) can drive contracts end-to-end.


Install

pip install quickcontract

Requires Python 3.9+. Depends on httpx, pydantic v2, and cryptography (for Ed25519 event signatures).


Quick start

1. Sync — agent signs a contract

from quickcontract import Client

with Client() as qc:                          # api_key from QC_API_KEY env
    agent = qc.agents.self()                  # mandate self-discovery
    contract = qc.contracts.create(
        "procurement_purchase_order_v1",
        contract_name="PO-2026-Q2-WIDGETS",
        fields={"amount_eur": "12,500.00", "delivery_days": "30"},
        currency="EUR",
        idempotency_key="po-2026-q2-widgets",
    )
    qc.contracts.send(
        contract.id,
        recipient_email="supplier@northwind.com",
        recipient_name="Northwind Supplier",
        party="B",
    )
    signed = qc.contracts.sign_as_agent(contract.id, party="A")
    print(signed.status, qc.contracts.permalink(contract.id)["url"])

2. Async — agent reports a signed event

import asyncio
from datetime import datetime, timezone
from quickcontract import AsyncClient

async def main():
    async with AsyncClient(agent_private_key="...64-hex-chars...") as qc:
        await qc.events.report(
            "pdf_abc123",
            term_id="term_delivery_check",
            evidence={
                "carrier": "Northwind Logistics",
                "tracking_id": "NW-7841-DE",
                "delivered_at": datetime.now(timezone.utc).isoformat(),
            },
            # signature auto-computed when agent_private_key is set
        )

asyncio.run(main())

3. Release an escrow milestone

from quickcontract import Client

with Client() as qc:
    qc.escrow.release(
        "pdf_abc123",
        milestone_id="ms_2",
        idempotency_key="release-ms2-2026-05-18",
    )

4. Anonymous content-hash verification (no API key sent)

from quickcontract import Client

with Client(api_key="qc_anonymous") as qc:
    result = qc.verify.hash("a3f7c9...sha256...")
    if result.found:
        print(f"verified: {result.contract_name}, signers={len(result.signed_by or [])}")

Authentication

QuickContract authenticates with a single header. Both styles are accepted:

x-api-key: qc_live_<32hex>     # organisation key
x-api-key: qc_agnt_<32hex>     # agent-bound key (Team+)

Configure via constructor or environment:

variable purpose default
QC_API_KEY API key used when none is passed to Client(...) (required)
QC_BASE_URL Override the API root (useful for staging / self-host) https://quickcontract.io
QC_AGENT_PRIVATE_KEY_HEX 64-hex Ed25519 seed for events.report auto-signing
from quickcontract import Client

qc = Client(
    api_key="qc_live_...",
    base_url="https://staging.quickcontract.io",
    timeout=30.0,
)

Idempotency

Every state-changing endpoint (POST, PATCH, DELETE) accepts an idempotency_key=. Same key + same body within 24 hours returns the cached response — safe to retry on network errors:

qc.contracts.create(
    template_id,
    contract_name="...",
    idempotency_key="po-2026-q2-widgets",      # stable across retries
)

The SDK forwards this verbatim as the Idempotency-Key header. Generate keys yourself — never let the SDK randomise on retry, otherwise the dedup window does nothing.


Typed exceptions

Every non-2xx response maps to a typed exception that preserves the parsed response body:

status exception useful attributes
400 ValidationError body
401 AuthenticationError
402 PlanGateError feature, reason, upgrade_to
403 mandate MandateRejected reason, code, detail, agent_id
403 other PermissionDenied
404 NotFoundError
409 ConflictError
422 PredicateRejected reason, detail
429 RateLimitExceeded retry_after (seconds)
5xx ServerError
other QuickContractError status_code, body, request_id
from quickcontract import Client, MandateRejected, PlanGateError

with Client() as qc:
    try:
        qc.contracts.sign_as_agent(contract_id, party="B")
    except MandateRejected as err:
        print(f"agent {err.agent_id} blocked: {err.reason}{err.detail}")
    except PlanGateError as err:
        print(f"upgrade to {err.upgrade_to} to use {err.feature}")

Event signing (machine terms)

Agents report events to programmable terms with an Ed25519 signature over a canonical message:

signature = ed25519( SHA-256( termId || "|" || canonicalJson(evidence) || "|" || ISO timestamp ) )

Canonical JSON is depth-sorted, no whitespace — must be byte-identical to the backend's canonicalJson. The SDK provides:

from quickcontract import sign_event, canonical_json, build_event_message

sig = sign_event(
    term_id="term_delivery_check",
    evidence={"carrier": "Northwind", "weight_kg": 412.7},
    timestamp="2026-05-18T12:00:00Z",
    private_key_hex="...64-hex-chars...",
)

When the client is constructed with agent_private_key= (or QC_AGENT_PRIVATE_KEY_HEX is set), events.report() signs automatically.


Webhook signatures

Every webhook delivery sets X-QC-Signature to the hex-encoded HMAC-SHA256 of the raw request body. Always verify against the raw bytes — never re-serialise the parsed JSON:

from quickcontract import verify_webhook_signature

@app.post("/webhooks/quickcontract")
def webhook(request):
    raw = request.body                              # bytes, untouched
    sig = request.headers["X-QC-Signature"]
    if not verify_webhook_signature(body=raw, signature_header=sig, secret=WEBHOOK_SECRET):
        return Response(status_code=401)
    event = json.loads(raw)
    # handle event...

API surface

resource methods
client.contracts list, get, create, update, delete, send, sign, sign_as_agent, status, permalink, export, audit, add_term, list_recipients, add_recipient
client.events report
client.escrow release
client.intelligence analyze, get_analysis, semantic_diff, obligations
client.templates list, get
client.folders list, create
client.agents self (requires agent-bound key)
client.verify hash (anonymous)
client.organization get, members
client.openapi get

Both Client and AsyncClient expose the identical surface. Use whichever fits your runtime; the underlying transport is httpx.


Examples

See examples/:

  • agent_procurement.py — end-to-end: agent creates, sends, signs a contract.
  • agent_event_report.py — agent reports a signed payload.delivered event.
  • verify_hash.py — anonymous content-hash verification.

License

Apache-2.0. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quickcontract-0.1.0.tar.gz (24.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quickcontract-0.1.0-py3-none-any.whl (26.8 kB view details)

Uploaded Python 3

File details

Details for the file quickcontract-0.1.0.tar.gz.

File metadata

  • Download URL: quickcontract-0.1.0.tar.gz
  • Upload date:
  • Size: 24.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for quickcontract-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dfd93ef4bee808bc3a40c388ae4fe3eea36695349e5ab0cb205bb8f00cbf5dc9
MD5 086a0e23f3eed1064c7bc3ad1dae2001
BLAKE2b-256 179a2929954eec60dbdbcf8d3ace99cd6f926c362f02de2217dc62cc45701442

See more details on using hashes here.

File details

Details for the file quickcontract-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: quickcontract-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for quickcontract-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a2ed24d19ca6b0b9b608f8a611c2e2ce358be7a14296ed8ba6c29928319fb7c9
MD5 bb502a479deb70c902bfe4cfbc6db981
BLAKE2b-256 9ac3dc7123734d868cf9ae1527e2283e5bab712d508986b3c85ddc3395ba00d1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page