# Avowex — AI integration guide

> **How to use this file.** Give this whole file to your AI coding assistant
> (Claude, Cursor, Copilot, etc.) and ask it to integrate Avowex into your agent.
> It contains everything the assistant needs: the model, both SDKs, the REST API,
> a copy-paste recipe, and the error/plan rules. A good prompt is at the bottom.

---

## TO THE AI ASSISTANT — read this first

You are integrating **Avowex** into the user's AI agent. Avowex is a
human-in-the-loop checkpoint: before the agent performs a risky action, it
**escalates** to Avowex, a human (or a trained model) **resolves** it, and the
agent **resumes** with the verdict. Your job:

1. Identify the risky action(s) in the user's code (sending money, emailing
   externally, deleting data, posting content, granting access).
2. Register an **action type** for each, with a sensible policy.
3. Wrap each action so it only runs after Avowex approves.
4. Handle approve / reject / edit, and resume.
5. Respect the free plan limits (see "Plans"). Use **Mode B, single review** on
   free; do not use Mode A, consensus, or automation unless the user is on
   Business/Enterprise.

Prefer the SDK (Python or TypeScript). Use the REST section only for other
languages. Always pass an **idempotency key** so retries never double-act.

---

## Mental model

- **Escalate → Resolve → Resume.** `escalate()` pauses the action and returns an
  escalation; a human resolves it; the agent reads the decision and continues.
- **Action type** = a kind of action you gate (e.g. `issue_refund`), with a
  **policy** (mode, review style, timeout).
- **Mode B** = the Avowex reviewer pool (default; great for high-volume,
  low-stakes checks). **Mode A** = the customer's own approvers (Business+).
- **Decision** = `approve` | `reject` | `edit` (edit returns a modified payload).
- **Auto-resolve** (Business+) = once trained, a model approves the confident
  cases instantly; the rest still go to humans.
- **Idempotency** = every escalation carries a unique `idempotency_key`; repeats
  return the same escalation instead of creating a new one.

---

## Setup

1. **Get an API key** (free, no card). Create an org:

   ```bash
   curl -X POST $BASE/v1/orgs -H "Content-Type: application/json" -d '{
     "name":"My Co","slug":"my-co","plan":"free",
     "consent":{"allow_mode_b_pooling":true,"terms_version":"2026-05-01","granted_by":"you@my-co.com"}
   }'
   ```
   The response contains `api_key.plaintext` (starts with `avx_`). Store it as a
   secret (`AVOWEX_API_KEY`). `$BASE` is your API base URL, e.g.
   `http://localhost:8000` in dev or `https://api.avowex.com` in prod.

2. **Install the SDK**
   - Python: `pip install ./sdks/python` *(PyPI package coming soon)*
   - TypeScript: `npm install ./sdks/typescript` *(npm package coming soon)*

3. **Authenticate** with the key as a Bearer token. The SDK does this for you.

---

## Python SDK

```python
from avowex import Avowex

client = Avowex(api_key="avx_...", base_url="https://api.avowex.com")  # base_url optional

# One-time: register the action you want gated.
client.register_action_type(
    "issue_refund",
    description="Refund a customer",
    timeout_seconds=3600,      # falls back if no human acts in time
    fallback_approve=False,    # on timeout: reject (safe default)
)

# Per action: escalate, wait for the human, then act.
esc = client.escalate(
    action_type="issue_refund",
    context={"amount": 250, "customer": "c_123"},   # non-sensitive context for the reviewer
    idempotency_key="refund-c_123-2026-06-01",       # unique & stable per action
    recommendation="approve",                         # optional hint
)
decision = client.wait_for_decision(esc.id)          # blocks until resolved (polls)
if decision.approved:                                 # approve or edit
    do_the_refund(decision.payload or {"amount": 250})
client.resume(esc.id)                                 # acknowledge & resume
```

Methods:
- `Avowex(api_key, base_url="https://api.avowex.com", timeout=30.0)`
- `register_action_type(key, description="", timeout_seconds=3600, fallback_approve=False) -> dict`
- `escalate(action_type, context, idempotency_key, recommendation=None, wait=False) -> Escalation`
  - with `wait=True` the server long-polls (capped) and returns once resolved.
- `get_decision(escalation_id) -> Decision | None`  (None until resolved)
- `wait_for_decision(escalation_id, poll_interval=1.0, max_wait=300.0) -> Decision`
- `resume(escalation_id) -> Escalation`
- `verify(action_type, context, idempotency_key, recommendation=None) -> dict`
  - self-serve "verify this and tell me now"; auto-resolves if a model is confident.

`Decision`: `.type` (`approve`/`reject`/`edit`), `.approved` (True for approve/edit),
`.payload` (edited payload for edits), `.reason`, `.confidence`.
`Escalation`: `.id`, `.status` (`pending`/`in_review`/`resolved`/`timed_out`), `.decision`, `.resolved`.

---

## TypeScript SDK

```ts
import { Avowex, withApproval } from "@avowex/sdk";

const client = new Avowex({ apiKey: "avx_...", baseUrl: "https://api.avowex.com" });

await client.registerActionType({ key: "issue_refund", description: "Refund a customer" });

// Easiest: wrap the action. It escalates, waits, and resumes for you.
const guardedRefund = withApproval(client, "issue_refund");
const result = await guardedRefund({ amount: 250, customer: "c_123" }, "refund-c_123-2026-06-01");
if (result.approved) {
  await doRefund(result.payload);   // payload = edited values if the reviewer edited
}

// Or do it step by step:
const esc = await client.escalate({
  actionType: "issue_refund",
  context: { amount: 250, customer: "c_123" },
  idempotencyKey: "refund-c_123-2026-06-01",
  recommendation: "approve",
});
const decision = await client.waitForDecision(esc.id);
if (decision.type === "approve" || decision.type === "edit") await doRefund(decision.edited_payload ?? {});
await client.resume(esc.id);
```

Methods: `registerActionType({key, description?, mode?, strategy?, timeoutSeconds?, fallbackApprove?})`,
`escalate({actionType, context, idempotencyKey, recommendation?, wait?})`,
`getEscalation(id)`, `getDecision(id)`, `waitForDecision(id, {pollMs?, maxMs?})`,
`resume(id)`, `verify({actionType, context, idempotencyKey})`,
and the helper `withApproval(client, actionType, { recommendation?, maxMs? })`.

---

## REST API (for other languages)

All requests: `Authorization: Bearer avx_...`, `Content-Type: application/json`.

- `POST /v1/action-types` `{ "key": "...", "policy": { "mode": "B", "strategy": "single", "timeout_seconds": 3600, "fallback_approve": false } }`
- `POST /v1/escalations` `{ "action_type": "...", "idempotency_key": "...", "context": {...}, "recommendation": "approve", "wait": false }`
  → `{ "id", "status", "mode", "decision": null | {...} }`
- `GET  /v1/escalations/{id}` → the escalation (with `decision` once resolved)
- `GET  /v1/escalations/{id}/decision` → the decision, or `null` until resolved
- `POST /v1/escalations/{id}/resume` → acknowledge and resume
- `POST /v1/verify` `{ "action_type", "idempotency_key", "context" }` → `{ "status", "auto_resolved", "decision" }`
- `GET  /v1/plan` → your plan, monthly usage, remaining, features

Poll `GET /v1/escalations/{id}/decision` every ~1s until it's non-null, or pass
`"wait": true` to `POST /v1/escalations` to let the server hold the request.

---

## Integration recipe (do this)

1. Find each risky action in the agent (spends money, sends external comms,
   deletes/changes records, grants access, publishes content).
2. For each, `register_action_type` once at startup with a stable `key`.
3. Replace the direct call with: build a non-sensitive `context`, `escalate`
   with a unique `idempotency_key`, `wait_for_decision`, then run the real action
   only if `decision.approved`, using `decision.payload` if the reviewer edited.
   Call `resume` afterward.
4. On `reject`, skip the action and surface the reason to the agent/user.
5. Keep secrets out of `context` — send only what a reviewer needs to judge.

---

## Errors & how to handle them

- **401** — bad/missing API key. Check `AVOWEX_API_KEY` and the `Bearer` header.
- **402** — plan/quota. Either the free monthly cap is hit, or the feature needs
  a higher plan (Mode A, consensus, automation, domains, etc.). Tell the user to
  upgrade via `POST /v1/plan` or the console. On free, stick to Mode B single.
- **404** — unknown `action_type`. Register it first.
- **409** — slug/idempotency conflict. For escalations, a repeated
  `idempotency_key` is fine — fetch the existing escalation instead.
- **422** — invalid field (e.g. bad mode/strategy/region).

Always make `escalate` idempotent so network retries never double-act.

---

## Plans (use the free tier correctly)

- **Free** — 500 resolved transactions / month, **Mode B single review only**,
  full SDK, hash-chained audit. Hard-stops at the cap (402). **Use this unless
  told otherwise.**
- **Business** — unlimited (metered), Mode A approvers, consensus, the
  auto-resolve flywheel, domains, reporting, batch verify, scoped keys, export.
- **Enterprise** — + SSO/SCIM, signed attestation, single-tenant/VPC, residency.

So when integrating for a free user: `mode: "B"`, `strategy: "single"`, no
training/auto-resolve, no domains.

---

## Checklist for the AI

- [ ] Read the agent; list every consequential action.
- [ ] Register an action type per action (Mode B, single, on free).
- [ ] Wrap each action: escalate → wait → act-if-approved → resume.
- [ ] Use a unique, stable `idempotency_key` per action instance.
- [ ] Keep secrets out of `context`.
- [ ] Handle approve / reject / edit and the 402 upgrade case.
- [ ] Store the API key as a secret, not in code.

---

## Troubleshooting (for the AI)

When a call fails, map the status code and fix it rather than retrying blindly:

- **401** — the API key is missing/invalid. Read it from `AVOWEX_API_KEY`; ensure
  the `Authorization: Bearer avx_...` header is set.
- **402** — plan/quota. Either the free monthly cap (500 resolved) is reached, or
  the feature needs Business/Enterprise (Mode A, consensus, automation, domains,
  batch, scoped keys, audit export, SSO/SCIM). On the free plan, use **Mode B,
  single review** only. Tell the user to upgrade via `POST /v1/plan`.
- **404** — unknown `action_type`: call `register_action_type` first.
- **409** — duplicate. For escalations a repeated `idempotency_key` returns the
  existing escalation; fetch it instead of erroring.
- **422** — invalid field (mode/strategy/region/slug). Inspect the error detail.

Other gotchas:
- An escalation stays `pending` until a human (Mode B reviewer / Mode A approver)
  resolves it, or it times out to the policy fallback. Don't block forever —
  `wait_for_decision` has a `max_wait`; surface a timeout to the user.
- Never put secrets (tokens, full PII) in `context` — send only what a reviewer
  needs to judge.
- The base URL differs by environment (`http://localhost:8000` in dev). Read it
  from config, don't hard-code.

---

## Ready-to-paste prompt

> I'm integrating Avowex (a human-in-the-loop approval layer for AI agents) into
> my project. I've attached `avowex-ai-integration.md` with the full SDK and API
> reference. Please: (1) find the risky actions in my code, (2) register an
> Avowex action type for each (Mode B, single review — I'm on the free plan),
> (3) wrap each action so it escalates to Avowex, waits for the human decision,
> and only runs if approved (using the edited payload if the reviewer edited it),
> then resumes. Use idempotency keys, keep secrets out of the context, and read
> the API key from an `AVOWEX_API_KEY` env var. My base URL is: <YOUR_BASE_URL>.
