Metadata-Version: 2.4
Name: agenitry
Version: 0.2.0
Summary: The authorization gateway for AI agents — spend authority, audit trail, and LangChain tools in one package.
Project-URL: Homepage, https://agenitry.com
Project-URL: Documentation, https://github.com/concya/agenitry#readme
Project-URL: Repository, https://github.com/concya/agenitry
Author-email: Agenitry <ola@agenitry.com>
License-Expression: MIT
Keywords: agenitry,ai-agents,audit,authorization,compliance,langchain,observability,spend-authority
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1; extra == 'langchain'
Requires-Dist: pydantic>=2.0; extra == 'langchain'
Description-Content-Type: text/markdown

# Agenitry Python SDK

> **The authorization gateway for AI agents.**  
> One line to log. One line to authorize. One URL to verify.

## The problem

Your AI agent takes actions — reservations, orders, comps, purchases — but nobody controls what it can spend. You're flying blind with an open wallet.

**Agenitry** gives every agent action a permanent, verifiable record *and* lets you control what agents can do before they do it. Log an event in one line. Authorize spend in one line. Share a URL. Done.

## Install

```bash
pip install agenitry
```

Zero dependencies. Python 3.9+.

## Quick start

```python
from agenitry import Agenitry

agent = Agenitry(api_key="ag_your_key", venue_id="nobo-downtown")

# ── Audit trail ──────────────────────────────────────────────

# Log an event — fire and forget by default
agent.log(action="order_captured", amount=42.50, direction="inbound")

# Await confirmation if you need the event ID
event = agent.log(action="reservation_booked", amount=0, await_confirmation=True)
print(event["id"])  # evt_abc123

# Query events
result = agent.events(action="order_captured", limit=10)

# Get stats
stats = agent.stats(period="7d")
print(stats["total_inbound"], stats["event_count"])

# ── Spend authority ──────────────────────────────────────────

# Set a policy: max $500 per order, need approval above $200
agent.set_policy(
    agent_id="voice-agent",
    max_single_amount=500,
    max_daily_spend=5000,
    require_approval_above=200,
    allowed_actions=["order_captured", "reservation_booked", "comp_issued"],
)

# Authorize before the agent acts
auth = agent.authorize(action="order_captured", amount=127.00, agent_id="voice-agent")

if auth["approved"]:
    # Agent can proceed — mark as executed
    agent.execute(auth["authorization_id"])
    # Payment confirmed — settle it
    agent.settle(auth["authorization_id"])
else:
    # Denied or pending owner approval
    print(f"Denied: {auth.get('denial_reason')}")
```

## Spend Authority

Agenitry doesn't just record what happened — it controls what *can* happen. Think of it as a traffic light, not a dashcam.

### How it works

1. **Set a policy** — define limits for each agent (or venue-wide defaults)
2. **Authorize before acting** — every spend request goes through the policy engine
3. **Auto-approve, auto-deny, or pending** — based on your rules
4. **Execute → Settle** — track the full lifecycle

### The 5 checks (in order)

| # | Check | If failed | Example |
|---|-------|-----------|---------|
| 1 | **Max single amount** | Auto-denied | $36K order blocked by $500 limit |
| 2 | **Allowed actions** | Auto-denied | `refund_issued` not in whitelist |
| 3 | **Daily spend limit** | Auto-denied | Would exceed $5K/day |
| 4 | **Monthly spend limit** | Auto-denied | Would exceed $50K/month |
| 5 | **Approval threshold** | Pending | $340 order needs owner approval (threshold: $200) |

### Authorization lifecycle

```
PENDING → APPROVED → EXECUTED → SETTLED
                  ↘         ↘
                   DENIED    REVERSED
                          ↗
                   EXPIRED
```

### Methods

```python
# Request authorization — always await this
auth = agent.authorize(action="order_captured", amount=127.00)

# Mark as executed (auto-creates an audit trail event)
agent.execute(auth["authorization_id"])

# Settle — payment confirmed, permanently closed
agent.settle(auth["authorization_id"])

# Reverse — cancel an approved or executed authorization
agent.reverse(auth["authorization_id"])

# List authorizations with filters
agent.authorizations(status="pending", agent_id="voice-agent")

# Get a single authorization
agent.get_authorization(auth["authorization_id"])
```

### Policies

```python
# Create or update a policy (per-agent or venue-wide)
agent.set_policy(
    agent_id="voice-agent",       # None = venue-wide default
    max_single_amount=500,        # Max per transaction
    max_daily_spend=5000,         # Rolling 24-hour limit
    max_monthly_spend=50000,      # Rolling 30-day limit
    require_approval_above=200,   # Amount that triggers human approval
    allowed_actions=["order_captured", "reservation_booked", "comp_issued"],
)

# List all policies
policies = agent.get_policies()

# Delete a policy
agent.delete_policy(policy_id)
```

## LangChain Integration

Agenitry ships with built-in LangChain tools so your agents can request authorization before spending money:

```bash
pip install agenitry langchain langchain-openai
```

```python
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from agenitry.langchain import create_authorize_tool, create_log_event_tool

# Create tools backed by your Agenitry policy
authorize = create_authorize_tool(
    api_key="ag_your_key",
    venue_id="nobo-downtown",
    agent_id="voice-agent",
)

log_event = create_log_event_tool(
    api_key="ag_your_key",
    venue_id="nobo-downtown",
    agent_id="voice-agent",
)

# Use with any LangChain agent
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a restaurant voice agent. Always authorize before taking orders."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, [authorize, log_event], prompt)
executor = AgentExecutor(agent=agent, tools=[authorize, log_event])

# The agent will now check spend authority before placing orders
result = executor.invoke({"input": "I'd like to order the omakase dinner for 2"})
```

### Available LangChain tools

| Tool | Description |
|------|-------------|
| `create_authorize_tool()` | Request spend authorization before taking financial actions |
| `create_log_event_tool()` | Log events to the audit trail |
| `create_set_policy_tool()` | Create or update spend policies (admin use) |

Each tool is a proper `StructuredTool` with typed inputs, descriptions, and error handling.

## Verify URL

Every event gets a permanent, shareable URL:

```
https://agenitry.com/verify/{venue_id}/{event_id}
```

No login required. No dashboard needed. Just share the link and anyone can verify what happened.

## API Reference

### `Agenitry(api_key, venue_id, *, agent_id=None, base_url=None, max_retries=3, retry_base_delay=0.5)`

Create a new Agenitry client.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `api_key` | `str` | required | Your venue API key (`ag_...`) |
| `venue_id` | `str` | required | Your venue identifier |
| `agent_id` | `str` | `None` | Default agent ID for all events |
| `base_url` | `str` | `https://api.agenitry.com` | API base URL |
| `max_retries` | `int` | `3` | Max retry attempts on 429/5xx |
| `retry_base_delay` | `float` | `0.5` | Base delay in seconds (exponential backoff) |

### Audit Trail

#### `agent.log(action, *, amount=None, currency=None, direction=None, agent_id=None, context=None, reason=None, await_confirmation=False)`

Log an event to the audit trail.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `action` | `str` | required | Event action (see below) |
| `amount` | `float` | `None` | Dollar amount |
| `currency` | `str` | `None` | ISO-4217 currency code |
| `direction` | `str` | `None` | `inbound`, `outbound`, or `internal` |
| `agent_id` | `str` | constructor default | Agent that performed the action |
| `context` | `dict` | `None` | Arbitrary JSONB context data |
| `reason` | `str` | `None` | Human-readable reason |
| `await_confirmation` | `bool` | `False` | If `True`, waits for server response |

**Fire and forget** (default): returns immediately with `{id: "", status: "logged"}`. If the request fails, it's silently swallowed.

**Await confirmation**: waits for the server response and raises `AgenitryError` on failure.

#### `agent.events(*, agent_id=None, action=None, context=None, limit=50, offset=0)`

Query events for the venue.

#### `agent.stats(*, period="today")`

Get aggregate stats. Period: `today`, `7d`, `30d`, or `all`.

### Spend Authority

#### `agent.authorize(action, amount, *, agent_id=None, currency=None, context=None, reason=None)`

Request spend authorization. **Always await this** — you need to know if the action is approved before proceeding.

Returns a dict with `approved` (bool), `status` (`approved`/`denied`/`pending`), `authorization_id`, and `denial_reason` if denied.

#### `agent.execute(authorization_id, *, actual_amount=None, context=None)`

Mark an authorization as executed. Auto-creates a linked event in the audit trail.

#### `agent.settle(authorization_id, *, context=None)`

Settle an executed authorization — permanently close it after payment is confirmed.

#### `agent.reverse(authorization_id)`

Reverse an approved or executed authorization. Creates a reversal event in the audit trail.

#### `agent.authorizations(*, agent_id=None, action=None, status=None, limit=50, offset=0)`

List authorizations with filters.

#### `agent.get_authorization(authorization_id)`

Get a single authorization by ID.

### Policies

#### `agent.set_policy(*, agent_id=None, max_single_amount=None, max_daily_spend=None, max_monthly_spend=None, require_approval_above=None, allowed_actions=None)`

Create or update a spend policy. If `agent_id` is provided, the policy applies only to that agent. If `None`, it's the venue-wide default.

#### `agent.get_policies()`

List all spend policies for the venue.

#### `agent.delete_policy(policy_id)`

Delete a policy by ID.

### Event Actions

| Action | Description | Direction | Example |
|--------|-------------|-----------|---------|
| `order_captured` | Agent captured an order | `inbound` | Voice agent took a $42.50 takeout order |
| `reservation_booked` | Agent booked a reservation | `inbound` | Chatbot reserved table 7 for 8pm |
| `price_changed` | Agent changed a price | `internal` | Agent updated happy hour draft from $6 to $7 |
| `item_86d` | Agent marked an item as unavailable | `internal` | Agent 86'd the tuna special |
| `comp_issued` | Agent issued a comp | `outbound` | Agent comped dessert for a regular |
| `purchase_order` | Agent placed a purchase order | `outbound` | Agent ordered 50 lbs of salmon |

### Authorization Statuses

| Status | Meaning |
|--------|---------|
| `pending` | Awaiting owner approval (above threshold) |
| `approved` | Auto-approved or owner-approved |
| `denied` | Auto-denied or owner-denied |
| `expired` | Approved but not executed within 5 minutes |
| `executed` | Action has been carried out |
| `settled` | Payment confirmed, permanently closed |
| `reversed` | Cancelled after approval/execution |

### `AgenitryError`

Raised on API errors. Attributes:

| Attribute | Type | Description |
|-----------|------|-------------|
| `status` | `int` | HTTP status code (0 if no response) |
| `body` | `dict` or `None` | Parsed response body |
| `code` | `str` | Error code: `NETWORK`, `TIMEOUT`, or `API` |

### `create_agenitry(api_key, venue_id, **kwargs)`

Factory function. Returns an `Agenitry` instance. Same arguments as the constructor.

## Type Aliases

```python
EventAction = Literal[
    "order_captured", "reservation_booked", "price_changed",
    "item_86d", "comp_issued", "purchase_order"
]

EventDirection = Literal["inbound", "outbound", "internal"]

StatsPeriod = Literal["today", "7d", "30d", "all"]

AuthorizationStatus = Literal[
    "pending", "approved", "denied", "expired", "reversed", "executed", "settled"
]
```

## License

MIT