← Developers

SDKs

A Python package that wraps the Intent API, so integrating an agent never means hand-implementing ED25519 signing, certificate headers, or retry logic.

PythonLIVE

The PayReality SDK (payreality) is a client, not a platform change: it consumes the same /v1/principals, /v1/agents, /v1/intents, and /v1/decisions endpoints that exist on the API today. Nothing about the Decision Engine, the Compiler, OPA, or Evidence changes because the SDK exists; it just removes the parts a developer shouldn't have to hand-roll.

The package isn't on public PyPI yet, so pip install payreality doesn't resolve today; build it from source (python -m build in sdk-python/) or install the wheel from a release artifact in the meantime. A public PyPI release is prepared, not shipped.

You don't need a production credential, or the platform Operator Key, to try any of this. A sandbox organization, its own real backend authority path, is self-service:

get_sandbox.sh
curl -X POST https://api.aisecurewatch.com/v1/sandbox/organizations \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

# -> {"organization_id": "...", "api_key": "pr_live_...", "starter_policy_key": "..."}

That response is a real, scoped API key for a real, isolated sandbox organization on the same production backend, already carrying one starter Runtime Policy (a low-risk reference action, allowed with no configuration) so your very first authorize() call below returns a real ALLOW, not just a stub. See the Getting Started guide for sandbox limits and what happens to a sandbox organization over time.

Register an agent

quickstart.py
from payreality import Agent

agent = Agent(bearer_token="pr_live_...", base_url="https://api.aisecurewatch.com")
identity = agent.register(name="AP Automation Agent", principal="Finance Manager")

print(identity.agent_id)         # server-assigned, never hand-picked
print(identity.certificate_id)   # the certificate this agent signs with

register() generates an ED25519 key pair locally, uploads only the public key, and returns a ready-to-use identity in one call. Registration is idempotent per key: calling register() again with the same private key returns the cached identity instead of creating a duplicate agent, which makes it safe to call on every process start rather than something you need to guard yourself.

Registering an agent establishes its identity. It doesn't, by itself, grant that agent any authority; that's the runtime's own job, evaluated per request. The SDK is a thin developer interface into PayReality, not a local copy of your governance logic: your Runtime Policies and Authority Graph live, and are evaluated, on PayReality's side, every time you call authorize().

Submitting an action

submit_intent.py
decision = agent.authorize(
    principal="Finance Manager",
    operation="submit_invoice",
    resource="Vendor Payment",
    resource_data={
        "amount": 8500,
        "currency": "USD",
        "vendor": "Acme Supplies",
    },
)

if decision.outcome == "ALLOW":
    ...  # proceed
elif decision.outcome == "HUMAN_REVIEW":
    ...  # a person will resolve this in the Review Queue

authorize() signs the request with the agent's own certificate, exactly like every signed Intent the API accepts. There are exactly three outcomes: ALLOW, DENY, or HUMAN_REVIEW. An action the platform doesn't recognize is never silently allowed: it's escalated to a human, the same fail-closed behavior the Decision Engine applies everywhere.

Calling authorize() doesn't ask PayReality to decide whether the action is a good idea. It asks the runtime to independently check the request against the policy your organization already set, the same approval matrices and spending limits that govern your human workforce, now evaluated against an agent instead of a person. The organization's policy is what authorizes the action; authorize() is the call that evaluates it.

What authenticates register(), and what doesn't

register(), rotate_keys(), and retire() are administrative actions, so they need an administrative credential: either Agent(bearer_token=...) (a session token or a scoped API key, like a sandbox credential, an organization's own/v1/organization/api-keys, or the sandbox response above), or Agent(api_key=...), the platform-wide Operator Key (see Authentication), an internal, administrative credential, not something a new external developer is issued. Prefer bearer_token for anything beyond the platform's own internal use. Neither is required for authorize() or heartbeat(), which authenticate purely via the agent's own certificate signature, the same way a human employee's badge doesn't require their manager's key to walk through a door they're already authorized for.

Issuing and verifying a Capability Authorization

capability.py
# After an ALLOW decision:
capability = agent.request_capability(decision.decision_id, audience="reference-pep")

# Or after a Human Review decision has since been approved:
capability = agent.request_capability_from_review(decision.decision_id, audience="reference-pep")

# At the enforcement checkpoint, before letting the downstream operation proceed:
consumed = agent.verify_capability(
    capability.token,
    audience="reference-pep",
    action="supplier_bank_details_change",
    resource="supplier:SUPPLIER_482",
    constraints={},
)

request_capability() and request_capability_from_review() authenticate the same way register() does: with a bearer token or API key if you have one, falling back to the Operator Key (naming its target organization) otherwise. Either can issue at most one Capability per decision, ever. verify_capability() is the checkpoint's own call: it consumes the Capability exactly once, rechecking that the organization, the originating agent, and any Trusted Integration identity it depends on are still active immediately beforehand, and fails closed if one isn't.

Why the SDK is synchronous

agent.authorize(...) is called directly, no await. This matches the default surface most developers already expect from a first SDK release (Stripe, OpenAI, and Supabase all ship synchronous clients as their default), keeping the initial surface area matched to what most integrations actually need. An async client is a natural, additive future addition, not a redesign.

Retries and error handling

Connection failures, timeouts, and 5xx responses are retried automatically with capped exponential backoff. 401, 403, and any other 4xx (including 422 validation failures) are never retried, since none of these can succeed by trying again unmodified. Every failure, network or HTTP, is mapped onto a typed exception before it reaches your code: you never see a raw HTTP client exception or a bare status code.

Language roadmap

Python ships first because it's what most agent frameworks in this space are already written in (LangGraph, CrewAI, AutoGen, the OpenAI and Anthropic SDKs). The following are planned, not started: there is no partial implementation to preview yet:

Node.jsPLANNED
GoPLANNED
JavaPLANNED
.NETPLANNED
RustPLANNED

For the full lifecycle methods (rotating keys, heartbeats, retiring an agent), see Integration Examples.