Skip to main content

Configuration

This page is the long-form walkthrough of how to configure the SDK and the dashboard for your workload. For quick lookup of types and defaults, see SDK API reference.

Where configuration lives

ProofRail configuration lives in two places:
  1. proofrail.init() in your application — SDK behavior (thresholds, fail modes, sanitization, fast-path). Pass values as keyword arguments.
  2. Dashboard at app.proofrail.dev — custom policies, approvers, agent registry, kill switch, budget configuration.
The SDK does not currently read configuration from environment variables — every value must be passed to init(). If you want to keep secrets like api_key out of your code, read them from your own env var or secrets manager and pass the value to init() yourself.

Minimal configuration

The smallest viable init:
import proofrail

proofrail.init(api_key="prail_...")
This uses every default. For a real workload you’ll probably want at least:
import os
import proofrail

proofrail.init(
    api_key=os.environ["PROOFRAIL_API_KEY"],   # read it yourself
    environment="production",
    external_domains_allowlist=["clients.com", "vendor.com"],
    fallback_approvers=["lead@company.com"],
)
The PROOFRAIL_API_KEY env var here is a convention you control — the SDK doesn’t look for it automatically.

Environment tagging

environment="production" (or "staging", "development") tags every chain with that environment. The dashboard filters chains by environment, so you can keep production and staging audit trails separate without separate accounts. The default is "production". Don’t leave this as the default if you’re also running locally — your test runs will show up alongside real production activity. A common pattern:
import os
import proofrail

proofrail.init(
    api_key=os.environ["PROOFRAIL_API_KEY"],
    environment=os.environ.get("APP_ENV", "development"),
)

Financial thresholds

Two thresholds, both in USD:
proofrail.init(
    api_key="...",
    financial_approval_threshold_usd=5000.0,        # single transactions
    cumulative_financial_threshold_usd=10000.0,     # chain cumulative
)
Single-transaction threshold — any one action with amount, amount_usd, or value exceeding this requires approval. Default: $5,000. Adjust for your business; SaaS startups can use the default, e-commerce or fintech may want lower. Cumulative chain threshold — sum of all financial values across the chain. Default: $10,000. This is the threshold that catches the “many small transactions” pattern that per-call governance misses. For workflows where any financial action should be reviewed, set both to small numbers (e.g., $100 and $500). For workflows where only the largest commitments need review, set them high. The hard-deny threshold for single transactions ($50,000 by default, non-configurable in the SDK) sits above these. Above that, the action is denied outright with no approval option. To change the hard-deny threshold, create a custom policy in the dashboard.

External domain allowlist

proofrail.init(
    api_key="...",
    external_domains_allowlist=["clients.com", "vendor.com", "*.example.com"],
)
Domains in this list are considered “internal” for the purposes of exfiltration checks. Sending data to allowlisted domains doesn’t trigger approval; sending to non-allowlisted domains does. Wildcard subdomains are supported (*.example.com matches api.example.com, mail.example.com, etc.). In production environment, sending to a non-allowlisted domain is a hard deny (the action is blocked, not just approval-required). In development and staging, it triggers approval instead. If you need different behavior, override with a custom policy in the dashboard.

High-risk agents

proofrail.init(
    api_key="...",
    high_risk_agents=["payment-agent", "deploy-agent", "data-export-agent"],
)
Every action by these agents requires approval, regardless of what the action is. Use this for agents that have access to high-stakes tools and shouldn’t run unsupervised. This is functionally equivalent to setting the agent’s risk_tier to high in the dashboard agent registry. The SDK-side list is for code-managed configuration; the dashboard is for ops-managed configuration. They merge — an agent is high-risk if it’s in either list.

Approval timeout and fallback

proofrail.init(
    api_key="...",
    default_approval_timeout_hours=24,
    fallback_approvers=["lead@company.com", "cto@company.com"],
)
Timeout — approvals expire and auto-deny after this many hours. Default 24. Use a short timeout (0.25 for 15 minutes) for chatbots and customer-facing workflows; a long timeout (168 for 7 days) for batch jobs that can wait. Fallback approvers — if no primary approver responds in half the timeout window, the approval escalates to this list. Default empty (no fallback). If you only need fallback (no specific primary list), the SDK uses your organization’s full approver list as primary and the configured list as fallback.

Failure handling: per-action-class fail modes

The most important configuration when the backend is unreachable.
proofrail.init(
    api_key="...",
    fail_modes={
        "financial": "deny",                # money operations: fail closed
        "external_communication": "deny",   # sending data out: fail closed
        "production_write": "deny",         # modifying prod data: fail closed
        "destructive": "deny",              # delete operations: fail closed
        "default": "allow",                 # everything else: keep working
    },
    backend_timeout_seconds=5.0,
    offline_buffer_max_events=100,
)
When the SDK can’t reach the backend within backend_timeout_seconds:
  1. The action is classified locally
  2. fail_modes is consulted for that action class
  3. If "deny"BackendUnavailableError is raised
  4. If "allow" → the action proceeds; the event is buffered locally and sent later
The buffer holds up to offline_buffer_max_events events. When the backend returns, buffered events are drained. If the buffer fills before connectivity returns, the oldest events are dropped with a warning logged. Why per-class? A single binary fail_mode="deny" would lock your application up the moment ProofRail’s backend has a blip. A single fail_mode="allow" would let high-risk actions through during outages. Per-class lets you fail closed on the actions that matter and stay productive on the ones that don’t. Legacy compatibility. If you pass the old fail_mode="deny" or fail_mode="allow", the SDK treats it as {"default": "deny"} or {"default": "allow"}. Don’t pass both fail_mode and fail_modes. Default fail_modes config — if you don’t pass fail_modes, the SDK uses the conservative default shown above (financial/communication/production_write/destructive deny, everything else allow).

Payload sanitization

ProofRail uses two separate lists for redacting sensitive data — one matches on field names, the other on value prefixes.
proofrail.init(
    api_key="...",
    sensitive_field_patterns=["internal_user_id", "customer_dob"],
    sensitive_value_patterns=["acme_token_"],
    max_payload_string_length=2000,
)
sensitive_field_patterns — field-name substrings (case-insensitive) that get redacted as "[REDACTED]". The SDK iterates over dict keys and redacts any key whose name contains one of these substrings. Default list:
["api_key", "password", "secret", "token", "credit_card", "ssn", "private_key"]
sensitive_value_patterns — string-value prefixes that get redacted. The SDK uses str.startswith() to match string values and redact anything beginning with one of these. Default list:
["sk_", "pk_", "ghp_", "hf_", "eyJ", "AKIA", "prail_"]
These two lists cover different attack vectors. Field patterns catch obvious cases (a password field in a payload). Value patterns catch leaked credentials in places they shouldn’t be (an OpenAI key accidentally pasted into a payload value, or — critically — a ProofRail key starting with prail_ being logged back through the SDK by mistake). Custom patterns you pass via these kwargs are appended to the defaults, not replacing them. max_payload_string_length — strings longer than this are truncated before sending. Default 1000 characters. Raise it if you need richer payloads in your audit trail; lower it if your payloads are huge (transcripts, large JSON blobs) and bandwidth matters. Raw payloads are never persisted by ProofRail — only the sanitized version reaches storage. If you redact something locally, it’s gone for good from our side.

Fast-path local evaluation

proofrail.init(
    api_key="...",
    enable_local_fast_path=True,  # default
)
When enabled, the SDK decides obviously-safe actions locally without a backend round-trip. This drops latency from 50-200ms per action to under 5ms for the ~70-80% of actions that match safe patterns. When to disable — strictest governance configurations. If you need the kill switch to halt every single action (including reads), or if your compliance posture requires that every decision be backed by a backend audit, set this to False. Every action will then go through the backend. The fast-path’s criteria for local decisions:
  • Action matches the SDK’s safe-action allowlist (read operations, non-financial tool calls)
  • No chain-level thresholds are near (financial exposure, communication count, etc.)
  • Environment is not flagged as production-critical
When in doubt, leave it enabled. The fast-path is conservative — anything ambiguous goes to the backend.

Backend URL override

proofrail.init(
    api_key="...",
    backend_url="https://staging-api.proofrail.dev",  # or self-hosted
)
Default: https://api.proofrail.dev. Override for:
  • Testing against a staging deployment
  • Self-hosted deployments (future capability — see Limitations)
  • Local development with the backend running on http://localhost:8001

Chain metadata

The metadata parameter on proofrail.Chain(...) accepts arbitrary JSON-serializable key/value pairs and stores them on the chain record. The SDK treats this as opaque — it does not interpret specific keys to override SDK config.
async with proofrail.Chain(
    "high-stakes-workflow",
    metadata={
        "team": "platform",
        "workflow_version": "2.1.0",
        "trace_id": "req_abc123",
    },
) as chain:
    ...
Metadata appears in the dashboard chain detail view and in the chain’s receipt. Useful for cross-referencing with your own systems but not for changing SDK behavior on a per-chain basis. If you need different approval timeouts or approver lists for different workflows, the recommended approach is to call proofrail.init() once per workflow boundary with the appropriate config, or to define custom policies in the dashboard that match on chain name.

Custom policies

Beyond the configurable thresholds above, ProofRail supports custom policies authored in the dashboard. They match on event attributes and apply your chosen decision. Custom policies are scoped: they can apply globally, to a specific environment, to specific agents, or to specific chains via name or metadata matching. Each custom policy has a mode (enforce, shadow, disabled) — new policies default to shadow so you can calibrate before enforcement. Custom policy authoring lives in the dashboard at /dashboard/policies. The detailed authoring guide is in the dashboard itself; this page covers the SDK-side configuration only. When a custom policy matches, the matching policy_name and decision_reason appear in the PolicyDecision returned from record_agent_action. For denials, they’re on the raised ActionDeniedError.

Common configurations

A few starting points: Solo developer testing
import os
import proofrail

proofrail.init(
    api_key=os.environ["PROOFRAIL_API_KEY"],
    environment="development",
    fail_modes={"default": "allow"},  # don't block during testing
)
Production service with strict financial controls
import os
import proofrail

proofrail.init(
    api_key=os.environ["PROOFRAIL_API_KEY"],
    environment="production",
    financial_approval_threshold_usd=500.0,
    cumulative_financial_threshold_usd=2000.0,
    external_domains_allowlist=["partner.example.com"],
    high_risk_agents=["payment-agent"],
    fail_modes={
        "financial": "deny",
        "external_communication": "deny",
        "default": "allow",
    },
    default_approval_timeout_hours=4,
    fallback_approvers=["cto@company.com"],
)
Highest-trust deployment (kill-switch matters more than latency)
import os
import proofrail

proofrail.init(
    api_key=os.environ["PROOFRAIL_API_KEY"],
    environment="production",
    enable_local_fast_path=False,  # every action goes to backend
    fail_modes={"default": "deny"},  # any backend issue halts agents
    backend_timeout_seconds=10.0,
)

Where to go next

SDK API reference

Type signatures and method docs.

Exceptions

How configuration choices surface as errors.

Policies

Concept page on how policies get evaluated.

Human approval

Approval timeouts and fallback in practice.