Python SDK
Install, configure, and use the Aira Python SDK. Two-step authorize and notarize flow, sync and async clients, framework integrations, and more.
Installation
pip install aira-sdkGateway (zero-code integration)
Route any OpenAI or Anthropic call through Aira without changing your code — just your config:
from openai import OpenAI
from aira import gateway_openai_kwargs
client = OpenAI(
**gateway_openai_kwargs(aira_api_key="aira_live_..."),
api_key="sk-...", # your OpenAI key
)
# Every call now authorized + notarized automaticallyfrom anthropic import Anthropic
from aira import gateway_anthropic_kwargs
client = Anthropic(
**gateway_anthropic_kwargs(aira_api_key="aira_live_..."),
api_key="sk-ant-...", # your Anthropic key
)See the Gateway guide for streaming, custom upstream URLs, and policy configuration.
Quick Start
Aira uses a two-step flow. Call authorize() before the agent acts to get a policy decision. Call notarize() after the agent executes to mint the receipt.
from aira import Aira, AiraError
aira = Aira(api_key="aira_live_...")
try:
auth = aira.authorize(
action_type="wire_transfer",
details="Send 75,000 EUR to vendor X",
agent_id="payments-agent",
)
except AiraError as e:
if e.code == "POLICY_DENIED":
# A policy blocked this action. Do not execute.
log.error(f"Blocked: {e.message}")
return
raise
if auth.status == "authorized":
# Execute the actual action
result = send_wire(75000, to="vendor")
# Step 2: notarize the outcome and mint the receipt
receipt = aira.notarize(
action_uuid=auth.action_uuid,
outcome="completed",
outcome_details=f"Sent successfully. ref={result.id}",
)
print(receipt.signature) # Ed25519 signature
elif auth.status == "pending_approval":
# Held for human review. Agent must wait.
# When the human approves, the action moves to status="approved"
# and the agent can call notarize() (e.g., from a webhook handler).
queue.enqueue(auth.action_uuid)See the Quickstart for more on the three branches (authorized, pending_approval, POLICY_DENIED).
Method Signatures
# Step 1: request authorization
auth = aira.authorize(
action_type: str,
details: str,
agent_id: str | None = None,
agent_version: str | None = None,
instruction_hash: str | None = None,
model_id: str | None = None,
model_version: str | None = None,
parent_action_uuid: str | None = None,
endpoint_url: str | None = None,
store_details: bool = False,
idempotency_key: str | None = None,
require_approval: bool = False,
approvers: list[str] | None = None,
)
# Returns Authorization with:
# .action_uuid (UUID string)
# .status ("authorized" | "pending_approval")
# .created_at
# .request_id
# .warnings (list[str] | None)
#
# Raises AiraError(code="POLICY_DENIED") on policy deny.
# Step 2: notarize the outcome after execution
receipt = aira.notarize(
action_uuid: str,
outcome: str = "completed", # "completed" or "failed"
outcome_details: str | None = None,
)
# Returns ActionReceipt with:
# .action_uuid
# .status ("notarized" | "failed")
# .receipt_uuid (None when failed)
# .payload_hash (None when failed)
# .signature (Ed25519, None when failed)
# .timestamp_token (RFC 3161, may be None on TSA failure)
# .created_at
# .request_id
# .warningsError Handling
Aira raises AiraError for all backend errors. Check e.code to discriminate:
from aira import Aira, AiraError
try:
auth = aira.authorize(action_type="wire_transfer", details="...")
except AiraError as e:
if e.code == "POLICY_DENIED":
# Action blocked by policy. Do not execute.
log.error(f"Blocked by policy: {e.message}")
log.error(f"Policy ID: {e.details.get('policy_uuid')}")
log.error(f"Action audit row: {e.details.get('action_uuid')}")
return
if e.code == "DUPLICATE_REQUEST":
# Idempotency key collision
return
if e.code == "ENDPOINT_NOT_WHITELISTED":
# Endpoint not on the org whitelist
return
raiseAsync Support
from aira import AsyncAira, AiraError
async with AsyncAira(api_key="aira_live_...") as aira:
try:
auth = await aira.authorize(
action_type="contract_signed",
details="Signed vendor agreement #1234",
agent_id="procurement-agent",
)
except AiraError as e:
if e.code == "POLICY_DENIED":
return
raise
if auth.status == "authorized":
result = await execute_contract_signing()
await aira.notarize(
action_uuid=auth.action_uuid,
outcome="completed",
outcome_details=f"Signed. doc_id={result.id}",
)Agent Registry
# Register
agent = aira.register_agent(
agent_slug="support-agent-v2",
display_name="Customer Support Agent",
capabilities=["email", "chat", "tickets"],
public=True,
)
# Publish version
aira.publish_version(
slug="support-agent-v2",
version="1.0.0",
model_id="claude-sonnet-4-6",
changelog="Initial release",
)
# Update
aira.update_agent("support-agent-v2", description="Updated description")
# List versions
versions = aira.list_versions("support-agent-v2")
# Decommission
aira.decommission_agent("old-agent")
# Transfer ownership
aira.transfer_agent("my-agent", to_org_uuid="org-uuid", reason="M&A")Evidence & Discovery
# Create sealed evidence package
package = aira.create_evidence_package(
title="Q1 2026 Lending Audit Trail",
action_uuids=["act-uuid-1", "act-uuid-2", "act-uuid-3"],
description="All lending decisions for BaFin review",
)
# Time-travel query
result = aira.time_travel(
point_in_time="2026-03-20T00:00:00Z",
agent_slug="lending-agent",
)
# Liability chain
chain = aira.liability_chain("act-uuid", max_depth=10)Agent Estate
# Set succession plan
aira.set_agent_will(
slug="support-agent",
successor_slug="support-agent-v3",
succession_policy="transfer_to_successor",
data_retention_days=2555,
notify_emails=["compliance@acme.com"],
instructions="Transfer conversation history. Delete PII after 7 years.",
)
# Get will
will = aira.get_agent_will("support-agent")
# Issue death certificate (agent must be decommissioned first)
aira.decommission_agent("old-agent")
cert = aira.issue_death_certificate("old-agent", reason="Replaced by v3")
# Compliance snapshot
snapshot = aira.create_compliance_snapshot(
framework="eu-ai-act",
agent_slug="lending-agent",
findings={"art_12_logging": "pass", "art_14_oversight": "pass"},
)Escrow
# Create account
account = aira.create_escrow_account(
purpose="Vendor contract #4521",
currency="EUR",
)
# Record commitment
aira.escrow_deposit(account.id, amount=5000.00, description="Liability commitment")
# Release
aira.escrow_release(account.id, amount=5000.00)
# Dispute
aira.escrow_dispute(account.id, amount=5000.00, description="Agent error in contract terms")Ask Aira (Chat)
response = aira.ask("How many email actions were notarized this week?")
print(response["content"])
print(response["tools_used"]) # ["count_actions"]Public Verification
No authentication required:
result = aira.verify_action("action-uuid")
print(result.valid) # True
print(result.message) # "Action receipt exists and signing key is valid."Trust Layer
Standards-based identity and trust for agents: W3C DIDs, Verifiable Credentials, mutual notarization, and reputation scoring. See the full Trust Layer guide for architecture details.
DID Identity
Every registered agent gets a W3C-compliant DID (did:web):
# DID is assigned on registration
agent = aira.register_agent(
agent_slug="my-agent",
display_name="My Agent",
capabilities=["email", "chat"],
)
# agent.did → "did:web:airaproof.com:agents:my-agent"
# Rotate keys
aira.rotate_agent_keys("my-agent")Verifiable Credentials
# Get the agent's W3C Verifiable Credential
vc = aira.get_agent_credential("my-agent")
# Verify it
result = aira.verify_credential(vc)
print(result["valid"]) # True
# Revoke
aira.revoke_credential("my-agent", reason="Agent deprecated")Mutual Notarization
For high-stakes actions, both parties co-sign:
# Agent A initiates
request = aira.request_mutual_sign(
action_uuid="act-uuid",
counterparty_did="did:web:partner.com:agents:their-agent"
)
# Agent B completes
receipt = aira.complete_mutual_sign(
action_uuid="act-uuid",
did="did:web:partner.com:agents:their-agent",
signature="z...",
signed_payload_hash="sha256:..."
)Reputation Score
rep = aira.get_reputation("my-agent")
print(rep["score"]) # 84
print(rep["tier"]) # "Verified"Endpoint Verification
aira.set_endpoint_policy(
mode="strict",
whitelist=["https://api.stripe.com", "https://api.openai.com"],
)Framework Integrations
LangChain
AiraCallbackHandler notarizes every tool call, chain completion, and LLM invocation. No changes to your chain logic.
from aira import Aira
from aira.extras.langchain import AiraCallbackHandler
aira = Aira(api_key="aira_live_xxx")
handler = AiraCallbackHandler(client=aira, agent_id="research-agent", model_id="gpt-5.2")
# Every tool call and chain completion gets a signed receipt
result = chain.invoke({"input": "Analyze Q1 revenue"}, config={"callbacks": [handler]})CrewAI
AiraCrewHook.for_crew() returns callback dicts that plug directly into CrewAI's Crew() constructor. Every task and step completion produces a court-admissible receipt.
from aira import Aira
from aira.extras.crewai import AiraCrewHook
aira = Aira(api_key="aira_live_xxx")
callbacks = AiraCrewHook.for_crew(client=aira, agent_id="research-crew")
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
**callbacks, # task_callback + step_callback — each notarized
)
crew.kickoff()OpenAI Agents SDK
AiraGuardrail.wrap_tool() wraps any tool function to automatically notarize both invocation and result with cryptographic proof.
from aira import Aira
from aira.extras.openai_agents import AiraGuardrail
aira = Aira(api_key="aira_live_xxx")
guardrail = AiraGuardrail(client=aira, agent_id="assistant-agent")
# Wrap tools — every call and result gets a signed receipt
search = guardrail.wrap_tool(search_tool, tool_name="web_search")
execute = guardrail.wrap_tool(code_executor, tool_name="code_exec")Google ADK
AiraPlugin provides before_tool_call and after_tool_call hooks that create cryptographic receipts at each stage of tool execution.
from aira import Aira
from aira.extras.google_adk import AiraPlugin
aira = Aira(api_key="aira_live_xxx")
plugin = AiraPlugin(client=aira, agent_id="adk-agent", model_id="gemini-2.0-flash")
# Hook into ADK tool lifecycle — receipts at invocation and completion
plugin.before_tool_call("search_documents", args={"query": "contract terms"})
result = search_documents(query="contract terms")
plugin.after_tool_call("search_documents", result=result)AWS Bedrock
AiraBedrockHandler.wrap_invoke_model() wraps your Bedrock client so every model invocation is notarized with a tamper-proof receipt.
import boto3
from aira import Aira
from aira.extras.bedrock import AiraBedrockHandler
aira = Aira(api_key="aira_live_xxx")
handler = AiraBedrockHandler(client=aira, agent_id="bedrock-agent")
bedrock = boto3.client("bedrock-runtime")
bedrock.invoke_model = handler.wrap_invoke_model(bedrock)
# Every invoke_model call now produces a cryptographic receipt
response = bedrock.invoke_model(modelId="anthropic.claude-v2", body=payload)MCP Server
Expose Aira as an MCP tool server. Any MCP-compatible AI agent (Claude Desktop, Cursor, Windsurf, etc.) can notarize actions and verify receipts without SDK integration.
export AIRA_API_KEY="aira_live_xxx"
aira-mcpThe server exposes four tools: authorize_action, notarize_action, verify_action, and get_receipt. The MCP-connected agent calls authorize_action before acting and notarize_action after.
Add to your MCP client config:
{
"mcpServers": {
"aira": {
"command": "aira-mcp",
"env": { "AIRA_API_KEY": "aira_live_xxx" }
}
}
}CLI
pip install aira-sdk[cli]aira version # SDK version
aira verify <action-uuid> # Verify a receipt
aira actions list --agent lending-agent --limit 20
aira agents list # List registered agents
aira agents create my-agent --name "My Agent"
aira snapshot create eu-ai-act lending-agent
aira package create --title "Q1 Audit Trail" --actions "uuid-1,uuid-2"All commands accept --api-key / -k and --base-url flags, or read from AIRA_API_KEY.
Offline Mode
Offline mode is for post-hoc audit actions where authorize has already run (or is not required). It queues pending records locally and flushes them when connectivity returns.
aira = Aira(api_key="aira_live_...", offline=True)
# Queue a two-step record locally. authorize and notarize are serialized.
auth = aira.authorize(
action_type="scan_completed",
details="Scanned document batch #77",
agent_id="scanner-agent",
)
# Execute the real work offline...
aira.notarize(
action_uuid=auth.action_uuid,
outcome="completed",
outcome_details="Batch 77 scanned, 412 pages",
)
print(aira._queue.pending_count)
# Flush to API when back online. Policies are evaluated server-side at flush time.
results = aira.sync()Offline mode means policies are not checked until sync() runs. Use it for audit-only workflows, not for gated actions.
Session Context Manager
Pre-fill defaults for a block of related actions. Every authorize() call within the session inherits the agent identity and model.
with aira.session(agent_id="onboarding-agent", model_id="claude-sonnet-4-6") as sess:
auth = sess.authorize(action_type="identity_verified", details="Verified customer ID #4521")
if auth.status == "authorized":
verify_identity_in_db(4521)
sess.notarize(
action_uuid=auth.action_uuid,
outcome="completed",
outcome_details="KYC check passed",
)
# Subsequent calls inherit the agent_id and model_id defaults
contract_auth = sess.authorize(
action_type="document_generated",
details="Generated contract for customer #4521",
)
if contract_auth.status == "authorized":
contract = build_contract(4521)
sess.notarize(
action_uuid=contract_auth.action_uuid,
outcome="completed",
outcome_details=f"Contract {contract.id} generated",
)Webhook Verification
Verify that incoming webhooks are authentic Aira events. HMAC-SHA256 signature verification ensures tamper-proof delivery.
from aira.extras.webhooks import verify_signature, parse_event
is_valid = verify_signature(
payload=request.body,
signature=request.headers["X-Aira-Signature"],
secret="whsec_xxx",
)
if is_valid:
event = parse_event(request.body)
print(event.event_type) # "action.notarized"
print(event.data) # Action data with cryptographic receiptSupported event types: action.authorized, action.approval_requested, action.approved, action.denied, action.notarized, action.failed, action.cosigned, agent.registered, agent.decommissioned, evidence.sealed, escrow.deposited, escrow.released, escrow.disputed, compliance.snapshot_created, case.complete, case.requires_human_review.
All Methods
Actions
| Method | Description |
|---|---|
authorize() | Step 1. Request permission before the agent acts. |
notarize() | Step 2. Report the outcome and mint the receipt. |
get_action(id) | Get action detail with policy evaluation and receipt. |
list_actions() | List actions with filters. |
approve_action(id) | Approve a held action (JWT auth). |
deny_action(id) | Deny a held action (JWT auth). |
cosign_action(id) | Human co-signature on a notarized action (JWT auth). |
set_legal_hold(id) | Prevent deletion. |
release_legal_hold(id) | Remove legal hold. |
get_action_chain(id) | Chain of custody. |
verify_action(id) | Public verification. |
Agents
| Method | Description |
|---|---|
register_agent() | Register identity |
get_agent(slug) | Get detail + versions |
list_agents() | List with status filter |
update_agent(slug) | Update metadata |
publish_version(slug) | Publish new version |
list_versions(slug) | List all versions |
decommission_agent(slug) | Decommission |
transfer_agent(slug) | Transfer ownership |
get_agent_actions(slug) | Actions by this agent |
Evidence
| Method | Description |
|---|---|
create_evidence_package() | Seal actions into bundle |
list_evidence_packages() | List packages |
get_evidence_package(id) | Get detail |
time_travel(point_in_time) | Point-in-time query |
liability_chain(action_uuid) | Multi-hop chain |
Estate
| Method | Description |
|---|---|
set_agent_will(slug) | Set succession plan |
get_agent_will(slug) | Get will |
issue_death_certificate(slug) | Issue death cert |
get_death_certificate(slug) | Get cert |
create_compliance_snapshot() | Attestation |
list_compliance_snapshots() | List snapshots |
Escrow
| Method | Description |
|---|---|
create_escrow_account() | Create account |
list_escrow_accounts() | List accounts |
get_escrow_account(id) | Get detail |
escrow_deposit(id, amount) | Record commitment |
escrow_release(id, amount) | Release commitment |
escrow_dispute(id, amount, desc) | File dispute |
Trust Layer
| Method | Description |
|---|---|
get_agent_did(slug) | Retrieve agent's W3C DID |
rotate_agent_keys(slug) | Rotate Ed25519 signing keys |
get_agent_credential(slug) | Get W3C Verifiable Credential |
verify_credential(vc) | Verify a Verifiable Credential |
revoke_credential(slug) | Revoke a credential |
request_mutual_sign(action_uuid, did) | Initiate mutual notarization |
complete_mutual_sign(...) | Complete mutual notarization |
get_mutual_sign_status(action_uuid) | Check mutual sign status |
get_reputation(slug) | Get reputation score and tier |
list_reputation_history(slug) | List reputation history |
set_endpoint_policy(...) | Set endpoint verification policy |
get_endpoint_policy() | Get current endpoint policy |
resolve_did(did) | Resolve any DID to DID Document |
check_trust(slug) | Run full trust check |
list_credentials(slug) | List all credentials |
get_trust_bundle(slug) | Get DID + VC + reputation |
Chat
| Method | Description |
|---|---|
ask(message) | Natural language query |
Error Handling
from aira.client import AiraError
try:
auth = aira.authorize(
action_type="email_sent",
details="Send onboarding email",
agent_id="support-agent",
)
except AiraError as e:
if e.code == "POLICY_DENIED":
# A policy blocked this action
print(f"Blocked: {e.message}")
elif e.code == "PLAN_LIMIT_EXCEEDED":
print("Monthly operation limit reached")
else:
print(f"[{e.code}] {e.message} (status={e.status})")See Error Handling for the full list of error codes.
Configuration
aira = Aira(
api_key="aira_live_xxx",
base_url="https://your-self-hosted.com", # Self-hosted
timeout=60.0, # Request timeout
)SDKs & Integrations
Aira SDKs for Python and TypeScript, plus framework integrations for LangChain, CrewAI, OpenAI, Vercel AI, and more.
TypeScript SDK
Install, configure, and use the Aira TypeScript SDK. Two-step authorize and notarize flow, async client, framework integrations for Vercel AI, LangChain.js, and OpenAI Agents.