Docs/Python Guide
Python 3.10+

Python Integration Guide

Govern AI content in Python — either on-device with tork-governance, or server-side via the REST API. Includes FastAPI, Flask, and Django integrations.

Prerequisites

  • Python 3.10 or higher
  • No API key is needed for on-device governance. A Tork API key is required only for the REST examples (get one here)
  • pip or poetry for package management

Two different packages — pick deliberately

tork-governance (PyPI) is the on-device package. Its import root is tork_governance and its entry class is Tork. tork-governance makes the governance decision on your machine. PII detection, redaction and the returned verdict are computed on-device. With no API key it makes zero network calls: prompts, completions and detected PII values never leave the machine, and nothing appears in your dashboard.

The REST API (and @torknetwork/sdk on npm) sends content to Tork, which makes the decision server-side and writes a receipt to your dashboard. Different package, different trust model. There is no published Python SDK for this path — use requests or httpx against /api/v1/govern, as shown below.

TorkClient and AsyncTorkClient are not part of tork-governance and never have been. Importing them from it raises an ImportError.

Local governance is free and unlimited — decisions made on your own machine are never metered. Evidence is what is metered: attestations, receipts and anchoring.

Installation

Install the on-device package, or just an HTTP client for the REST API.

bash
# ON-DEVICE — the decision is made on your machine.
# Import root is tork_governance; the entry class is Tork.
pip install tork-governance

# CLOUD / REST — Tork makes the decision server-side and writes
# a receipt to your dashboard. No Python SDK is published for this
# path; call the REST API with requests or httpx.
pip install requests  # or httpx for async
bash.env
# .env
# Used by the REST examples below. The on-device package does not
# require an API key, and the published builds never send one.
TORK_API_KEY=tork_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Never commit your API key. Add .env to .gitignore and use python-dotenv to load it.

On-device SDK (tork-governance)

Decisions are made in-process. govern() is synchronous and does no I/O.

python
from tork_governance import Tork, GovernanceAction

# No API key: the decision is made on this machine and nothing
# is sent anywhere. This is free and unlimited.
tork = Tork()

result = tork.govern("Check this text for PII: john@example.com")

print(result.action)              # GovernanceAction.REDACT
print(result.output)              # "Check this text for PII: [EMAIL_REDACTED]"
print(result.receipt.receipt_id)  # "rcpt_..." — a LOCAL receipt, not in your dashboard

# GovernanceResult is a dataclass, not a dict. Use attribute access:
#   result.action    (not result["action"])
# Fields: action, output, pii, receipt, report.
if result.action is not GovernanceAction.ALLOW:
    print(f"PII types found: {result.pii.types}")

Server-governed REST API

Send content to Tork and get a dashboard receipt back. Requires an API key.

python
import os
import requests

TORK_API_KEY = os.environ["TORK_API_KEY"]
BASE_URL = "https://tork.network/api/v1"

def govern_content(content: str, mode: str = "redact") -> dict:
    """Govern content server-side. Tork makes the decision and
    writes a receipt to your dashboard."""
    response = requests.post(
        f"{BASE_URL}/govern",
        headers={
            "Authorization": f"Bearer {TORK_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "content": content,
            "options": {"mode": mode}
        }
    )
    response.raise_for_status()
    return response.json()

# Usage — this sends the content to Tork.
result = govern_content("Contact me at john@example.com")
print(result["action"])      # "allow" | "redact" | "deny"
print(result["output"])      # redacted text
print(result["receipt_id"])  # "rcpt_..." — appears in your dashboard

Error Handling & Retry Logic

Handle API errors gracefully with exponential backoff.

python
import time
import requests
from requests.exceptions import RequestException, HTTPError

class TorkAPIError(Exception):
    """Custom exception for Tork API errors."""
    def __init__(self, message: str, status_code: int = None, request_id: str = None):
        self.message = message
        self.status_code = status_code
        self.request_id = request_id
        super().__init__(self.message)

def govern_with_retry(
    content: str,
    max_retries: int = 3,
    backoff_factor: float = 0.5
) -> dict:
    """Govern content with exponential backoff retry."""

    for attempt in range(max_retries):
        try:
            response = requests.post(
                "https://tork.network/api/v1/govern",
                headers={
                    "Authorization": f"Bearer {TORK_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={"content": content},
                timeout=10
            )

            # Handle specific HTTP errors
            if response.status_code == 401:
                raise TorkAPIError("Invalid API key", 401)

            if response.status_code == 429:
                # Rate limited - wait and retry
                retry_after = int(response.headers.get("Retry-After", 60))
                if attempt < max_retries - 1:
                    time.sleep(retry_after)
                    continue
                raise TorkAPIError("Rate limit exceeded", 429)

            if response.status_code >= 500:
                # Server error - retry with backoff
                if attempt < max_retries - 1:
                    time.sleep(backoff_factor * (2 ** attempt))
                    continue
                raise TorkAPIError(f"Server error: {response.status_code}", response.status_code)

            response.raise_for_status()
            return response.json()

        except requests.Timeout:
            if attempt < max_retries - 1:
                time.sleep(backoff_factor * (2 ** attempt))
                continue
            raise TorkAPIError("Request timed out")

        except RequestException as e:
            raise TorkAPIError(f"Request failed: {str(e)}")

    raise TorkAPIError("Max retries exceeded")

HTTP Status Codes

200SuccessProcess response
400Bad requestFix request body, don't retry
401UnauthorizedCheck API key, don't retry
429Rate limitedWait and retry (check Retry-After)
500+Server errorRetry with exponential backoff

Framework Integrations

Ready-to-use examples for popular Python web frameworks

pythonmain.py
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from tork_governance import Tork, GovernanceAction

app = FastAPI()

# On-device: no client lifecycle, no network, nothing to close.
# Safe to construct once at module scope and share.
tork = Tork()

class ChatRequest(BaseModel):
    message: str
    user_id: str

class ChatResponse(BaseModel):
    response: str
    filtered: bool = False

# Dependency that governs the inbound message
def govern_input(request: ChatRequest):
    # No await — govern() is synchronous and does no I/O.
    result = tork.govern(request.message, session_id=request.user_id)

    if result.action is GovernanceAction.DENY:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "Content blocked by policy",
                "pii_types": [t.value for t in result.pii.types],
                "receipt_id": result.receipt.receipt_id,
            }
        )
    return result

@app.post("/chat", response_model=ChatResponse)
async def chat(
    request: ChatRequest,
    governed = Depends(govern_input),
):
    # Send the REDACTED text downstream, not the raw message.
    ai_response = await generate_response(governed.output)

    # Govern the model's output on the way back out too.
    output_result = tork.govern(ai_response)

    return ChatResponse(
        response=output_result.output,
        filtered=output_result.action is not GovernanceAction.ALLOW,
    )

Batch Processing

Evaluate multiple items efficiently with controlled concurrency.

python
from tork_governance import Tork, GovernanceAction
from typing import List, Dict, Any

# On-device governance is CPU-bound and local. There is no rate limit
# to respect and no concurrency to tune — a plain loop is the fastest
# correct answer. Async/semaphores would add overhead and buy nothing.
tork = Tork()

def govern_batch(items: List[str]) -> List[Dict[str, Any]]:
    results = []
    for content in items:
        result = tork.govern(content)
        results.append({
            "content": content[:50] + "...",
            "action": result.action.value,
            "pii_count": result.pii.count,
            "pii_types": [t.value for t in result.pii.types],
            "output": result.output,
        })
    return results

# Usage
messages = [
    "Hello, how are you?",
    "My email is john@example.com",
    "Card 4111 1111 1111 1111",
]

results = govern_batch(messages)

# Anything that wasn't simply allowed through
flagged = [r for r in results if r["action"] != GovernanceAction.ALLOW.value]
print(f"{len(flagged)} of {len(results)} messages were redacted or denied")

# For very large volumes, use multiprocessing — the work is CPU-bound,
# so processes (not threads or coroutines) are what actually scale it.

Reporting to your dashboard

Metadata-only attestation from the on-device SDK.

python
from tork_governance import Tork

# Passing an api_key turns ON metadata-only attestation reporting.
# The decision is STILL made on-device and is never delayed or
# changed by reporting. Tork never receives your input text,
# output text, or the PII values themselves.
tork = Tork(api_key="tork_live_...")

result = tork.govern("Contact me at john@example.com")

# The local decision is available immediately, as always.
print(result.action)   # GovernanceAction.REDACT
print(result.output)   # "Contact me at [EMAIL_REDACTED]"

# result.report describes the SEPARATE reporting attempt, which runs
# on a background thread. attempted and succeeded are independent —
# check succeeded, not just attempted.
if result.report:
    result.report.wait()          # optional; most callers don't need this
    print(result.report.attempted)
    print(result.report.succeeded)
    print(result.report.receipt_id)  # set only if the server persisted it
    print(result.report.reason)      # set only if it did not

# NOTE: this requires a build that is not on PyPI yet — see the
# release note below. Installing today gives you local governance
# with no reporting at all.

Supply an API key and the SDK additionally reports a metadata-only attestation of each decision — the action taken, PII type labels and counts, a risk classification, policy labels and a salted fingerprint. It never sends input text, output text or PII values. The decision itself is still made on-device and is never delayed or changed by reporting. Those attestations appear in your dashboard and are included in the daily on-chain anchor — each one recorded as a client attestation (capture_mode=edge, attested_by=client), a claim Tork recorded but did not execute and cannot independently verify. Requires tork-governance 0.24.0+ (PyPI) or 0.11.0+ (npm).

A decision reported by an on-device SDK is recorded as a client attestation (capture_mode=edge, attested_by=client): a claim Tork recorded but did not execute and cannot independently verify. A decision made by @torknetwork/sdk is recorded as capture_mode=cloud, attested_by=tork — Tork made that call itself. Both are equally immutable once anchored; they differ in what is immutable. A client attestation freezes your claim. A server-governed receipt freezes Tork's own decision.

Best Practices

Know which package you installed

tork-governance exports Tork and decides on-device. TorkClient belongs to @torknetwork/sdk and decides server-side. Mixing them is the single most common integration failure.

Pass the redacted output downstream

Use result.output when calling your model or logging. Passing the original text forward defeats the redaction you just performed.

Don't await govern() on-device

It is synchronous in both Python and JavaScript. There is no network call to wait for, and no client to close.

Use attribute access, not subscripts

GovernanceResult is a dataclass: result.action works, result['action'] raises TypeError.

Decide how YOUR call fails

Wrap your own governance call and choose whether an unexpected error in your code path allows or blocks the request. This is your error-handling policy, not a statement about Tork's availability.

Use middleware for web frameworks

Middleware provides consistent protection across all endpoints without code duplication.

Typed dataclasses

tork-governance returns dataclasses (GovernanceResult, PIIResult, Receipt) with inline annotations, so editors resolve result.action and result.pii.count. The distributed package does not yet ship a py.typed marker, so strict mypy/pyright runs will treat it as untyped unless you opt in explicitly.

Next Steps

See the API reference for the full /api/v1/govern contract, or the quickstart for the shortest path from install to a first decision.

Documentation

Learn to integrate TORK

Upgrade Plan

Current: free

Support

Get help from our team