Metadata-Version: 2.5
Name: authoxi
Version: 0.7.0
Summary: Python SDK for the authoxi authentication platform
Project-URL: Homepage, https://authoxi.com
Project-URL: Documentation, https://authoxi.com
Project-URL: Source, https://github.com/aeternm/authoxi/tree/main/sdk/python
Project-URL: Issues, https://github.com/aeternm/authoxi/issues
Author: aeternm
License-Expression: MIT
License-File: LICENSE
Keywords: agent-identity,auth,authentication,authoxi,mcp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: cryptography<50,>=42.0.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: fastapi>=0.110.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.110.0; extra == 'fastapi'
Description-Content-Type: text/markdown

# authoxi — Python SDK

The official Python client for [authoxi](https://authoxi.com): **authorization for AI agents**.
An agent asks before it acts, and every answer — allow, deny, or "a human must sign this" — is a
signed, offline-verifiable record.

```bash
pip install authoxi
```

## The free half: make your agent verifiable, with no account

Everything in this section works with **no authoxi account, no key from us, and no network
call to us** — and keeps working if you never become a customer. It is `authoxi.agentsig`,
the same code the [Agent Trust Grader](https://authoxi.com/grader) scores strangers with and
the same code our own control plane verifies with.

The problem it solves is not that signing is hard. It is that four pieces — a key, a
published directory, a request signature, and a record — have to line up exactly, and getting
one of them subtly wrong fails *silently*: your signatures verify locally and nobody else can
check them.

```bash
python -m authoxi.agentsig keygen        # an identity. Store the seed; it IS your agent.
```

```python
from authoxi.agentsig import AgentSigner

signer = AgentSigner.from_env()          # reads AUTHOXI_AGENT_KEY

# 1. Publish this at /.well-known/http-message-signatures-directory
signer.directory()

# 2. Sign outbound requests — to anyone, not to us. An origin, CDN or API that
#    speaks Web Bot Auth can now tell you apart from a scraper.
headers = signer.sign_request("GET", "https://api.partner.com/inventory")
httpx.get("https://api.partner.com/inventory", headers=headers)

# 3. Emit an audit record anyone can check later, offline, with no registry:
record = signer.sign_record({"tool": "search", "args_hash": "…"})
```

Verify anything, from anywhere, with no key:

```bash
echo "$RECORD" | python -m authoxi.agentsig verify     # exits non-zero on a forgery
```

A record signed this way says `"witness": "self"` on its face, and the CLI says so out loud.
That is deliberate: *"I signed my own log"* and *"an independent party countersigned it"* are
the difference between a record and a proof, and a format that let them look alike would be
doing its readers real harm.

**What paying adds** is the part a library cannot do for you: a *decision* (may this agent do
this, right now?), a *witnessed* record instead of a self-attested one, and someone else
holding a copy of the evidence when yours is the thing in question. That is the rest of this
README.

## Quickstart

In your agent, one line does the work:

```python
import authoxi

@authoxi.runtime().guard("payment", amount=lambda inv: inv["amount"])
def pay_invoice(inv):
    return psp.charge(inv)          # runs only if authoxi allows it
```

You write no `if`. On **deny** the body never executes and `Denied` is raised. On **escalate**
the body never executes either — by default the call *blocks* until a human signs (up to
`timeout=`, 120s), which is usually what a background worker wants. Pass `wait=False` to get
`Escalated` raised immediately and handle the resumption yourself:

```python
@authoxi.runtime().guard("payment", amount=lambda inv: inv["amount"], wait=False)
def pay_invoice(inv):
    return psp.charge(inv)
```

A callable `amount` is applied to the same arguments the function receives, so the amount you
authorized and the amount you charged stay one expression rather than two that drift apart.
That is the whole point: enforcement is structural, not something you remember to check.

`authoxi.runtime()` takes no arguments because an agent's URL, id, key and mandate are facts
about a *deployment*, not the program — set `AUTHOXI_BASE_URL`, `AUTHOXI_AGENT_ID`,
`AUTHOXI_AGENT_KEY` and `AUTHOXI_MANDATE_ID`. The key is a stored hex seed, so a booting agent
never imports a cryptography library to rebuild it.

The same object is also a block, when a decorator doesn't fit:

```python
with authoxi.runtime().guard("payment", "250.00"):
    psp.charge(invoice)
```

### Issuing the authority (your console, not your agent)

The agent above can only *ask*. Creating agents and issuing mandates needs the tenant secret
key, and lives on your server behind a human:

```python
from authoxi import AgentControl

cp     = AgentControl.from_url("https://api.authoxi.com", secret_key="sk_live_...")
agent  = cp.create_agent("procurement-bot")
wallet = agent.issue_mandate(budget="5000.00", escalate_over="1000.00", actions=["payment"])
```

`create_agent` generates an Ed25519 keypair locally and registers only
the **public** half — authoxi never sees the private key, so it cannot impersonate your agent.
The mandate is signed: `budget` is the hard ceiling, `escalate_over` is where the agent stops
being autonomous, and `actions` is exhaustive (anything not listed is denied). Money is always a
decimal string, never a float.

### When a human needs to sign off

Over the `escalate_over` threshold the action does **not** happen — `authorize` returns
`escalate`. `authorize_and_wait` blocks until a human decides, so there's no polling loop:

```python
verdict = wallet.authorize_and_wait("payment", "2500.00")   # over the threshold
if verdict.get("approved"):
    ...   # a human signed for this; the signature is what makes it non-repudiable
```

### Guard your own code

`guard()` wraps a call you already have, so it runs **only** if authoxi allows it. No proxy, no
sidecar — authoxi sees the decision, never your payload or your credential:

```python
with wallet.guard("payment", "2500.00"):
    stripe.PaymentIntent.create(...)   # your key, your call — raises Denied if refused
```

### Driving a fleet

The handles above are sugar over `AgentControl`, which names everything explicitly — the right
surface for an operator console governing many agents:

```python
cp.authorize(agent_id=..., mandate_id=..., action="payment", amount="250.00")
cp.list_agents(); cp.list_pending(); cp.revoke_agent(agent_id)
```

Bring your own key (an HSM, an existing identity) with `cp.register_agent(public_key=..., ...)`
instead of `create_agent`.

### Verify a decision offline

Anyone can check a decision record with no account, no key, and no call back to us:

```python
from authoxi import verify_event

result = verify_event(event)
assert result.ok
```

## Which client goes where

This matters more than it looks. `AgentControl` holds the tenant secret key, which can **issue**
authority — create agents, mint mandates, grant keyrings, raise budgets. Put it inside the agent
it governs and your policy is a suggestion: the agent can write itself an unbounded mandate with
no approver and authorize against it, and every signature will verify.

So ship `AgentRuntime` in the agent. It authenticates with the agent's own passport (proof of a
private key authoxi has never seen) and has **no method that can issue anything** — the most it
can do is ask:

```python
from authoxi import AgentRuntime

rt = AgentRuntime.from_url(
    "https://api.authoxi.com",
    agent_id="agt_...", private_key=priv,   # handed to it by your console
    mandate_id="mnd_...",
)

with rt.guard("payment", "2500.00"):
    stripe.PaymentIntent.create(...)        # runs only if authorized; blocks for a human if needed
```

| Class | Credential | Where it belongs |
|---|---|---|
| `AgentRuntime` | the agent's own passport | **inside the agent** — can ask, can never issue |
| `AgentControl` | tenant secret key (`sk_live_`) | **server-side**, behind a human: issuance + administration |
| `AuthoxiClient` | publishable key | **humans** — signup / signin / OTP / OAuth |

Both accept a URL (`AgentControl.from_url(...)`, `AuthoxiClient(base_url=...)`), or you can inject
any httpx-style client (handy for tests: pass a FastAPI `TestClient` straight in).

Errors are typed — `NotFound`, `Forbidden`, `Conflict`, `Unauthorized`, `InvalidRequest`, plus the
domain events `BudgetExceeded`, `Denied`, `Escalated` — each carrying `.code` and `.status_code`.

- **Docs:** https://authoxi.com
- **API:** https://api.authoxi.com
- **Source:** https://github.com/aeternm/authoxi/tree/main/sdk/python
- **JS SDK** (browser): [`@authoxi/js`](https://www.npmjs.com/package/@authoxi/js)

## License

MIT — see [LICENSE](./LICENSE). The SDK is MIT so you can embed it freely; the authoxi service itself
is proprietary.
