Verity
/Docs

Python SDK Reference

The official Python SDK for Verity. Async-first, built on httpx. Requires Python 3.10+. Identical behavior and guarantees as the TypeScript SDK.

pip install verityinc-sdk

VerityClient

The main entry point. Create one instance and reuse it throughout your application.

from verity import VerityClient

verity = VerityClient(
    base_url="https://api.useverity.io/v1",
    api_key=os.environ["VERITY_API_KEY"],
    namespace="payments",
)

Constructor Parameters

ParameterTypeDefaultDescription
base_urlstrrequiredVerity API base URL
api_keystrrequiredAPI key (vt_live_* or vt_test_*)
namespacestr | NoneNoneDefault namespace for all protect() calls
agent_idstr | NoneNoneDefault agent/worker identifier
auto_renewboolTrueAutomatically renew leases in the background
renew_at_fractionfloat0.65Fraction of lease duration at which renewal fires (0.3–0.9)
request_timeout_sfloat20.0Timeout for individual HTTP requests in seconds
conflict_retryConflictRetryConfigSee belowRetry behavior for 409 Conflict
loggerlogging.Logger | NoneNoneCustom logger. If None, uses logging.getLogger("verity")

ConflictRetryConfig

from verity import ConflictRetryConfig

config = ConflictRetryConfig(
    enabled=True,           # default
    max_attempts=12,        # default
    initial_delay_s=0.5,    # default
    max_delay_s=15.0,       # default
    jitter=True,            # default — ±30% random jitter
)

protect()

Protect a standalone effect. Async method — must be awaited.

result = await verity.protect(effect_key, observe=..., act=..., **params)

Parameters

ParameterTypeDescription
effect_keystrUnique key identifying this effect instance
observeCallable[[], Awaitable[T | None]]Optional. Check external system for prior completion.
actCallable[[], Awaitable[T]]Required. Execute the real-world action.
namespacestr | NoneNamespace override for this call
effect_namestr | NoneHuman-readable name
agent_idstr | NoneAgent identifier override
lease_duration_msint | NoneLease duration in milliseconds (5,000–120,000)
input_jsonAnyInput data for debugging (max 64 KB)
on_conflict"retry" | "throw""throw" raises LeaseConflictError immediately
on_leaseCallable[[LeaseResponse], None]Callback after lease is acquired
key_suffixstr | NoneAppended to effect key for cardinality

Example

import stripe
from verity import VerityClient

verity = VerityClient(
    base_url="https://api.useverity.io/v1",
    api_key=os.environ["VERITY_API_KEY"],
    namespace="payments",
)

async def check_existing_refund():
    existing = stripe.Refund.list(charge=charge_id, limit=1)
    return existing.data[0] if existing.data else None

async def execute_refund():
    return stripe.Refund.create(
        charge=charge_id,
        amount=4999,
        reason="requested_by_customer",
    )

refund = await verity.protect(
    "refund:order_48392",
    observe=check_existing_refund,
    act=execute_refund,
    effect_name="stripe.refund",
    lease_duration_ms=60_000,
    input_json={"order_id": order_id, "charge_id": charge_id},
)

Workflow Builder

Identical to the TypeScript workflow builder, but with Pythonic naming:

run = verity.workflow("refund_flow").case("order_123").run()

await run.protect(
    "validate_order",
    act=validate_order,
)

await run.protect(
    "process_refund",
    observe=check_existing_refund,
    act=execute_refund,
)

await run.protect(
    "notify_customer",
    act=send_notification_email,
)

run() Options

ParameterTypeDefaultDescription
run_idstr | NoneAuto-generatedExplicit run ID
namespacestr | Noneworkflow_nameNamespace override
lease_duration_msint | None30000Default lease duration for the run
agent_idstr | NoneNoneAgent identifier for the run

Low-Level API

For manual control over the lease lifecycle:

# Request a lease
lease = await verity.request_lease("payments", LeaseRequestBody(
    effect_key="refund:order_123",
    effect_name="stripe.refund",
))

# Commit a result
await verity.commit("payments", CommitRequestBody(
    effect_key="refund:order_123",
    fence_token=lease["fenceToken"],
    lease_token=lease["leaseToken"],
    result={"refund_id": "re_123"},
    source="acted",
))

# Record a failure
await verity.fail("payments", FailRequestBody(
    effect_key="refund:order_123",
    fence_token=lease["fenceToken"],
    lease_token=lease["leaseToken"],
    error={"message": "Stripe declined"},
    source="acted",
))

# Renew a lease
await verity.renew("payments", RenewRequestBody(
    effect_key="refund:order_123",
    fence_token=lease["fenceToken"],
    lease_token=lease["leaseToken"],
))

Error Classes

All errors extend VerityError. Identical semantics to the TypeScript SDK:

from verity import (
    VerityError,                    # Base class for all Verity errors
    VerityApiError,                 # Non-2xx API response
    LeaseConflictError,             # 409 — another agent holds the lease
    EffectPreviouslyFailedError,    # Effect previously failed — reset required
    CommitUncertainError,           # Action succeeded but commit failed — CRITICAL
    VerityConfigError,              # Missing required config
    VerityValidationError,          # Input data invalid or too large
)

Catching Errors

from verity import (
    CommitUncertainError,
    EffectPreviouslyFailedError,
    LeaseConflictError,
)

try:
    result = await verity.protect("refund:order_123", act=execute_refund)
except CommitUncertainError as e:
    # CRITICAL: action succeeded, commit failed
    # DO NOT RETRY — check Explorer to reconcile
    logger.critical(
        f"Commit uncertain for {e.effect_key}. "
        f"Result: {e.result}. Commit error: {e.commit_error}"
    )
    alert_ops_team(e)
except EffectPreviouslyFailedError as e:
    # Effect failed before — admin must reset in Explorer
    logger.warning(f"Effect {e.effect_key} previously failed: {e.cached_error}")
except LeaseConflictError as e:
    # Another agent is processing this effect
    logger.info(f"Effect {e.effect_key} is being processed by another agent")

See Error Handling for complete details on each error class.

Exports

from verity import (
    # Client
    VerityClient,

    # Config
    ConflictRetryConfig,

    # Errors
    VerityError,
    VerityApiError,
    VerityConfigError,
    VerityValidationError,
    LeaseConflictError,
    EffectPreviouslyFailedError,
    CommitUncertainError,

    # Request body types (for low-level API)
    LeaseRequestBody,
    CommitRequestBody,
    FailRequestBody,
    RenewRequestBody,
    ReportObserveBody,
)

TypeScript ↔ Python Naming

Both SDKs are feature-identical. The only differences are Pythonic naming conventions:

TypeScriptPython
baseUrlbase_url
apiKeyapi_key
agentIdagent_id
autoRenewauto_renew
renewAtFractionrenew_at_fraction
requestTimeoutMsrequest_timeout_s (seconds, not ms)
conflictRetryconflict_retry
effectKeyeffect_key
effectNameeffect_name
leaseDurationMslease_duration_ms
inputJsoninput_json
onConflicton_conflict
onLeaseon_lease
keySuffixkey_suffix