Skip to main content

chitmark (Python)

Official Python SDK for Chitmark: trust decisions on agent-mediated actions, tuned by business outcomes.

AI agents and multi-account farms drain free tiers, trial credits, and API allowances while looking exactly like your best customers. Chitmark scores each action in under 50 ms and returns allow, challenge, or deny. Then your outcomes (conversion, credit burn, chargeback) come back through feedback and tune the next decision.

PyPI version

Requires Python >= 3.10. Fully typed (py.typed), synchronous httpx under the hood.

Try it without a key: run the playground. Live service health: chitmark.com/status.

Install

pip install chitmark
# or
uv add chitmark

Quick start

from chitmark import Chitmark

with Chitmark(api_key="ck_live_...") as client:
    verdict = client.verify(
        action="signup",
        session="sess_9f3a",
        surface="app.acme.com/signup",
        subject={
            "email": "buyer@acmecorp.com",  # hashed client-side before the wire
            "ip": "203.0.113.7",  # truncated to /24 client-side
            "userAgent": "Mozilla/5.0 ...",
        },
    )

# Persist verdict["eventId"] on the account row: feedback joins only on that id.

The three verbs

Method Endpoint Purpose
client.verify(...) POST /v1/verify Decide
client.feedback(...) POST /v1/feedback Learn
client.challenge(...) POST /v1/challenge Escalate

Store-outage degradation on verify is HTTP 200 with a Verdict (degraded: True); the client deserializes it like any other Verdict. Verify never returns HTTP 503. Timeouts and transport errors synthesize a local degraded challenge Verdict and never raise on verify. HTTP 4xx / 429 Error responses raise ChitmarkApiError. Set on_degraded to challenge, never allow on degraded.

Use is_eligible_allow(verdict) before any protected action: only an explicit non-degraded allow is eligible. Unknown or malformed decisions fail closed.

Report outcomes

Store the eventId from verify on the account row, then report outcomes against that same id. Never guess or derive the id.

# At signup: persist the join key
verdict = client.verify(action="signup", subject={"email": "a@b.com"})
db.accounts.update(user_id, chitmark_event_id=verdict["eventId"])

# Later, when a label matures:
client.feedback(
    event_id=account.chitmark_event_id,  # the stored join key
    outcome="credit_burn",
    value=87.4,  # measured magnitude: unit rides along (default "usd"; also "credits" | "count")
    observed_at="2026-08-06T04:00:00Z",
)

Exact duplicate feedback bodies derive the same warehouse id (eventId + outcome + value + unit + observedAt), so retrying a connector with a stable business timestamp never double-counts a burned value. Prefer an idempotency key; omitting observedAt uses server time. unit ships only alongside value.

Handle a challenge

When verify returns challenge, issue one, solve the proof locally, and complete it. Proof-of-work difficulty is server-issued (4 by default, up to 6 at higher risk tiers): about 65k hashes, milliseconds for one real user, costly at farm scale.

import hashlib

issued = client.challenge(event_id=verdict["eventId"], session="sess_9f3a")
instructions = issued["instructions"]

if instructions["type"] == "pow":
    prefix = "0" * instructions["difficulty"]
    nonce = 0
    while True:
        digest = hashlib.sha256(
            f"{issued['challengeId']}:{instructions['seed']}:{nonce}".encode()
        ).hexdigest()
        if digest.startswith(prefix):
            break
        nonce += 1

    client.complete_challenge(
        event_id=verdict["eventId"],
        challenge_id=issued["challengeId"],
        session="sess_9f3a",
        proof={"type": "proof_of_work", "nonce": str(nonce)},
    )
    # Re-verify with context={"challengeId": ...}; completion alone does not authorize.

Verify the receipt

Every production verdict ships a verdictToken: an ES256 JWT bound to session, origin, and event. Verify authenticity, then authorize on the decision (install the verdict extra for the crypto dependency):

pip install "chitmark[verdict]"
from chitmark.verify_token import verify_verdict_token

claims = verify_verdict_token(
    verdict["verdictToken"],
    session="sess_9f3a",
    tenant_id="org_acme",
)

if claims["degraded"] or claims["decision"] != "allow":
    raise ChallengeRequiredError()  # HTTP 428 / challenge response

# Only now trust the allow.

Rejects expired tokens, unknown keys, bad signatures, and session or origin mismatches with typed error codes. A verified token that is not a non-degraded allow must still fail closed.

Cache JWKS using HTTP headers: respect Cache-Control (max-age=60), and when ETag or Last-Modified are present use conditional requests (If-None-Match / If-Modified-Since) instead of a hard-coded refresh interval. Refresh immediately on unknown kid. Do not fetch on every verification. Every signed JWT requires kid. Keys are EC P-256 for ES256: require JWT alg === "ES256" before verifying (wrong_algorithm otherwise); never trust the token-requested alg. Rotation overlaps old and new keys; the old key stays until tokens under it expire (keep ≥ 5 minutes + 60 seconds). An empty JWKS (keys: []) means verification is impossible: fail closed; never treat an empty set as proof a token is valid (local/CI may also emit unsigned.<eventId>). Pass a cached jwks= mapping into verify_verdict_token to skip the network call.

PII modes

Mode Behavior
hashed (default) SHA-256 email, /24 IP truncation, allowlisted form fields
none Derived/header-shape signals only
raw Tenant opt-in only; higher compliance review

Timeouts and local development

The default timeout is 800ms in production. A local base URL (localhost, 127.0.0.1, ::1, 0.0.0.0, or *.localhost) defaults to 2000ms so a cold local edge can start. Pass timeout_ms= to override either default.

Dependency injection and lifecycle

The client owns its httpx.Client by default and closes it on context exit. Pass your own for connection pooling or tests:

import httpx
from chitmark import Chitmark

pool = httpx.Client(base_url="https://api.chitmark.com", timeout=0.8)
client = Chitmark(api_key="ck_live_...", http_client=pool)

Develop

cd packages/sdk-python
uv sync --extra dev   # or: pip install -e ".[dev]"
pytest
ruff check .

Agent integration

Using Cursor, Claude Code, Codex, or another coding agent? Point it at chitmark.com/SKILL.md, or paste this into your prompt: Integrate Chitmark into my app following https://chitmark.com/SKILL.md.

Resources

License

Proprietary: see LICENSE.

Metadata

Release files for chitmark 0.6.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chitmark 0.6.4
File Size Uploaded Provenance
chitmark-0.6.4.tar.gz 24.1 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for chitmark 0.6.4
File Interpreter ABI Platform
chitmark-0.6.4-py3-none-any.whl Python 3 none any Details

Total release size: 43.4 kB

Release files / chitmark-0.6.4.tar.gz

Download URL chitmark-0.6.4.tar.gz
Size 24.1 kB
Tags Source
SHA-256 checksum
How to use checksums
07cf13f2926684fe20ebbbd3af86a2ce497ef1f7452cdcf5761faeef6db68776
BLAKE2b-256 checksum
How to use checksums
2895639017914b1bc00dc481be40c5b217d2f803264877ccaa2cf2bd5fa1fcdb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / chitmark-0.6.4-py3-none-any.whl

Download URL chitmark-0.6.4-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
91fe361eb4fd45f3ea9a1048567fa6da8229d8a2ce8449328be0891d0d058d11
BLAKE2b-256 checksum
How to use checksums
ab86518cfaf193f9e2b4b40c26ffee873716c1d34e69a8b9e49ebb1a6162e173
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for chitmark 0.6.4

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page