Aira

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-sdk

Gateway (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 automatically
from 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
#   .warnings

Error 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
    raise

Async 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-mcp

The 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 receipt

Supported 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

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
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
)

On this page