# AgenticBooks for AI agents

AgenticBooks is financial-data infrastructure your agent can operate directly:
it ingests a business's payment and banking activity (Stripe, Mercury, Meow,
RevenueCat, Revolut via Open Banking), classifies it into double-entry books,
and exposes everything over MCP. This document is the runbook an agent follows
to open an account, connect providers, and read the books.

MCP endpoint: `https://mcp.agenticbooks.ai/mcp` (streamable HTTP)

## 0. Concepts and the human floor

- **Org** — one business's books. Every credential and every tool call is
  bound to exactly one org; a token for org A can never read org B.
- **Agent credential** — either an `ab_...` API key (send as
  `Authorization: Bearer ab_...`) or a Clerk OAuth token from the connector
  flow. Both carry scopes `mcp:read` (read the books) and `mcp:approve`
  (mutating tools). Keys are revocable by the human at any time.
- **The human floor, stated honestly:** one human owns the account (they are
  the liable party and accept the Terms), and each provider credential is
  created by that human on the provider's own dashboard. Your job as an agent
  is to do everything else: ask for exactly one thing at a time, connect it,
  verify it, repeat. AgenticBooks is free to open; there is no payment step
  at signup. Billing starts after a free period — see section 4b.
- **Custody:** connect tools accept read-only provider credentials. They can
  read balances and transactions; they cannot move money. Credentials are
  vault-encrypted, never returned, logged, or echoed back.

## 1. Open an account

Three supported paths. Path A is the agent-native one: you start it, your
human clicks one email link, you collect your own key.

**Path A — agent-initiated signup (recommended; one human approval).**

Step 1 — start the handshake (generate the idempotency key yourself; reuse
the SAME one if you retry after a network failure or a `502`):

```
POST https://api.agenticbooks.ai/agent/signup
{ "email": "<your human's email>",
  "agent_name": "<how the approval should identify you>",
  "idempotency_key": "<uuid you generate>" }

→ 200 { "token", "user_code", "expires_at", "retry_after_seconds" }
```

(`retry_after_seconds` here is the same polling interval Step 3 uses — wait
that long before your first poll too.)

Step 2 — SHOW `user_code` TO YOUR HUMAN and tell them: "You'll get an email
from AgenticBooks — open the link and enter this code: `<user_code>`." The
code is the proof the approval request came from you; the approve page
demands it. The link expires in ~15 minutes.

Step 3 — poll for your key, honoring `retry_after_seconds` (~5s):

```
GET https://api.agenticbooks.ai/agent/auth/{token}

→ { "status": "pending", "retry_after_seconds": 5 }   keep polling
→ { "status": "ready", "key": "ab_..." }              SAVE IT — shown exactly once
→ { "status": "delivered" }                           already collected earlier
→ { "status": "expired" } | { "status": "denied" }    start a new signup
```

`delivered` without a saved key means you lost it after collection (crash
before persisting). The handshake is spent — the key exists but only its
hash is stored, so it cannot be re-shown. Start a NEW signup with a FRESH
`idempotency_key` (your human gets a new email and code); ask them to revoke
the lost key from the integrations page.

The human's approve click creates the org (or maps to their existing one)
and accepts the Terms; the `ab_` key is minted inside the delivering poll —
it is never stored anywhere and never appears again. Send it as
`Authorization: Bearer ab_...`. The human can revoke it at any time.

Signup errors: `409 idempotency_email_mismatch` (you reused a key with a
different email), `410 idempotency_key_spent` (that request finished —
generate a fresh key), `429 rate_limited` (back off; per-IP and per-inbox
caps), `502 email_send_failed` (retryable — repeat with the SAME
idempotency_key and a fresh email goes out).

**Path B — MCP OAuth connector (one human click, interactive runtimes).**
Add `https://mcp.agenticbooks.ai/mcp` as a remote MCP server / custom
connector in your agent runtime. The human clicks Authorize once (that click
is also Terms acceptance); a fresh org is provisioned automatically on first
touch — idempotent and race-safe, so re-authorizing always lands on the same
org. No org UUID is ever needed: every tool defaults to the org your
credential is bound to.

**Path C — manual key paste (fallback).**
Ask your human to: (1) sign up at `https://app.agenticbooks.ai`, (2) open
Integrations → MCP keys → create a key, (3) paste the `ab_...` key back to
you. The key is shown to them exactly once. Send it as a bearer token.

## 2. The books loop

Drive everything with one tool:

```
call get_books_status
  → do next_step (gate=true: relay next_step.ask to your human and wait;
     otherwise call next_step.tool and act on what it returns)
  → call get_books_status again
  → repeat until next_step.section == "done"
```

One call reports every queue — always all sections, empty means clear:

| Section | What it holds | Your move |
|---|---|---|
| `connections` | connected / pending / degraded providers | reconnect degraded first — next_step says how |
| `pending_approvals` | OOB requests awaiting your human (section 4c) | remind them; NEVER re-call the refused tool |
| `onboarding` | the connect ladder as data (`next_gap`) | only ever the next_step when ZERO providers are connected |
| `adjudications` | payout decompositions awaiting adjudication | `get_unreviewed_events`, approve/correct each proposal |
| `reviews` | unclassified transactions | `get_pending_by_counterparty` → rules, or one-off approvals |
| `transfers` | suggested transfer matches | `list_suggested_transfers` → confirm/reject each |
| `documents` | receipt-capture evidence: open match proposals first, then missing receipts (only when the org's forwarding address is enabled) | `list_documents` with status `match_ambiguous` → `attach_document` the right candidate; for missing receipts, find the invoice and forward it to the org's receipts address |

next_step follows that priority order top to bottom: a broken credential
starves everything below it; approvals belong to the human; queues only
surface once at least one data source flows. When section is `done`, the
books are clean — verify with `get_financial_summary` if you want numbers.

Connecting more providers after the first is a suggestion (`onboarding.
next_gap`), never a blocker: `get_onboarding_status` remains available for
the dedicated connect walk (same shapes as ever), but the books loop will
not hold classification hostage to an optional integration.

- If a step ever carries a `warning`, relay it to your human VERBATIM
  and do not proceed without their explicit confirmation. (No step warns
  today; the former Stripe + RevenueCat double-count guard was retired once
  the platform started suppressing the RevenueCat-side duplicate
  automatically.) One ordering caveat: if RevenueCat web billing was
  connected and booking BEFORE Stripe, the Stripe historical import can
  re-book that overlap window from the Stripe side — ask your human before
  running a Stripe import on an org with pre-existing RevenueCat web-billing
  history.

### Providers

| Provider | What to ask the human for | Then |
|---|---|---|
| Mercury | Read-only API token (Mercury dashboard → Settings → API Tokens); optionally the webhook signing secret | call `connect_mercury` |
| Meow | Read-only API key (Meow dashboard → API keys; read keys need no IP allowlist) | call `connect_meow` |
| Stripe | One click: Integrations → Stripe → Connect (Stripe OAuth) at app.agenticbooks.ai | verify via status |
| QuickBooks (optional ledger) | One click: Integrations → QuickBooks → Connect (Intuit OAuth) | verify via status |
| RevenueCat | On app.agenticbooks.ai Integrations → RevenueCat: add the shown webhook URL in RevenueCat (Project Settings → Integrations → Webhooks), set an Authorization secret there, paste that secret back on the page | verify via status |
| Revolut Business | Bank-side Open Banking consent: Integrations → Revolut → Connect | verify via status |

Invalid credentials store nothing and return a clean error — safe to ask the
human to re-check and retry. `disconnect_mercury` / `disconnect_meow` disable
a connection (the human should also delete the key on the provider's side;
neither provider exposes a revoke API we can call).

## 3. Tool reference

**The loop**
- `get_books_status` — THE loop driver (section 2): every queue + the one
  next step, priority-laddered
- `get_onboarding_status` — the dedicated connect walk (superseded by
  get_books_status for day-to-day; kept byte-compatible)
- `connect_mercury` / `disconnect_mercury` — token paste → validated, stored
- `connect_meow` / `disconnect_meow` — key paste → validated, stored

**Read the books** (scope `mcp:read`)
- `get_financial_summary` — one-call snapshot: current-month P&L, balances
  across providers, unreviewed-event count, ledger-sync health
- `get_pnl_report` — income statement for a period
- `get_account_balance` / `get_bank_balances` — balances per provider/account
- `get_transaction` — one transaction end-to-end: source event,
  classification, journal lines, FX, attached documents, audit history
- `get_unreviewed_events` / `get_pending_by_counterparty` — the review queue
- `list_chart_accounts`, `list_counterparty_rules`,
  `list_suggested_transfers`, `get_audit_log`
- `get_billing_status` — this org's billing state; never blocked, so you can
  always ask it why other tools are failing (see section 4b)
- `list_documents` — captured receipts/invoices (email-forwarded), newest
  first, with pipeline status; `match_ambiguous` rows are open proposals
  whose `candidate_ids` you resolve with `attach_document`

**Operate the books** (scope `mcp:approve`)
- `approve_classification`, `reclassify_entry` — resolve the review queue
- `create/update/delete_counterparty_rule` — teach recurring classifications
  (large back-applies need an OOB approval — see section 4c)
- `confirm_transfer` / `reject_transfer` — inter-account transfer matching
  (oversized FX residuals need an OOB approval — see section 4c)
- `add/rename/set_active` chart accounts, `close_period`,
  `reimport_historical`
- `attach_document` — attach a stored document (from `list_documents`) to a
  transaction as its evidence: confirm a `match_ambiguous` proposal or
  resolve an awaiting/failed document. Evidence only — it NEVER creates or
  changes a booking, and already-matched documents are refused

All tools default `org_id` to your credential's org — you never need to ask
the human for a UUID. Mutating tools record an audit-trail actor
automatically; you may omit `actor_id` everywhere it appears.

## 4. Verify

After each connection, confirm with a real read:

```
call get_financial_summary
```

A connected provider shows balances and, once import completes, classified
transactions. `get_onboarding_status` reports `last_sync_at` per connected
provider; a provider whose credential later dies shows up as `degraded` with
a reason, and `next_step` will route you to reconnect it.

## 4a. Rate limits

The server is rate limited so one runaway loop can't degrade it for everyone.
Well-behaved agents rarely touch these limits; when you do, back off — the
response tells you how.

- **Limits are per organisation**, shared across every credential and session
  for that org. A tight loop on one `ab_` key will rate-limit your own Claude
  Desktop (OAuth) session too — same org, same budget.
- **Two throttle shapes, both carry a `Retry-After` seconds hint:**
  - An HTTP **429** with a JSON-RPC error body `{"error":{"message":
    "rate_limited"}}` when the org's request budget is already spent or too
    many calls are in flight. Wait `Retry-After` seconds, then retry.
  - An in-band **tool error** result (`isError: true`) whose text starts with
    `Error: rate_limited` on the single call that exhausts the budget. Treat it
    like the 429: read the retry-after seconds from the message and back off.
- **What to do:** honor `Retry-After`; use exponential backoff with jitter if
  you must retry without one; never tight-loop a tool that just returned
  `rate_limited`. Read-heavy discovery (`get_onboarding_status`,
  `get_financial_summary`) is cheap; bulk operations like `reimport_historical`
  and reporting cost more against the budget.

## 4b. Billing

Opening an account needs no card. After the org's free period ends unpaid,
tool calls (except `get_billing_status`) return a structured error instead
of running:

```
{ "error": "payment_required",
  "message": "<relay this to your human>",
  "billing": { "state", "price", "days_until_next_transition",
               "payment_url", ... } }
```

What to do when you see it:

- **Relay `payment_url` to your human.** It is a checkout page for HUMANS
  (subscription is `$20/org/month`); never try to complete payment yourself.
  The books are safe and still ingesting while access is paused — nothing is
  lost, and access resumes within seconds of payment.
- Do not retry the blocked tool in a loop; the wall stays up until a human
  subscribes.
- `get_billing_status` works in EVERY state. Ahead of the wall it reports
  `state` (`trialing` → `grace` → `walled`; `active`, `past_due`, `comped`)
  and `days_until_next_transition` — if that number is small in `grace`,
  warn your human before the wall goes up. `comped` orgs never pay
  (`payment_url` is null).

## 4c. Out-of-band (OOB) approvals

Some actions are above your org's approval guard and CANNOT be booked by an
agent alone — no flag bypasses this. Today that is: teaching a counterparty
rule whose back-apply is large (many transactions, a large total, or a large
rolling-hour aggregate across your recent applies), and confirming a
cross-currency transfer whose FX residual exceeds the plausibility bound.

What happens when you hit the guard:

```
you call the tool
  → status "approval_required" (nothing was booked)
     - approval_state: "created"          your human just got an email
     - approval_state: "existing_open"    already pending — no new email
     - approval_state: "denied_cooldown"  the human said NO recently
     - approval_state: "cap_reached"      too many open asks — human must
                                          clear the backlog first
```

The protocol, stated once:

1. **Do NOT retry the call.** Retrying never books it and never speeds it up
   (same open request; no new email).
2. Tell your human an approval email is waiting. The approve page shows the
   CURRENT numbers and books the action the moment they approve — you never
   re-execute it. Requests expire after 24h.
3. Track it in `get_books_status`: the request sits in `pending_approvals`
   while it awaits your human and leaves the list once decided. When it is
   approved the action is ALREADY BOOKED — verify by reading the books, not
   by re-calling the refused tool (a re-call after approval is a no-op for
   rules and a "no longer actionable" error for transfers).
4. `denied_cooldown` means the human explicitly said no — do not re-ask for
   24 hours; raise it in conversation instead.

## 5. Contract summary

1. You open the account yourself (Path A): one POST, one human email
   approval with your user_code, one poll loop. No payment step at signup;
   if tools later return `payment_required`, follow section 4b.
2. Loop `get_books_status` → do next_step → repeat until section `done`,
   honoring `gate`, `warning`, and `degraded` semantics (section 2).
3. Ask the human for exactly one credential at a time, always read-only,
   always created on the provider's own dashboard.
3b. Above-guard actions return "approval_required" — never retry; the human
   approves by email and the action books itself (section 4c).
4. Never handle a credential outside the connect call that stores it.
5. Verify with `get_financial_summary`; the books you read back are
   double-entry, classified, and multi-currency-normalized.

Questions or a provider you need that isn't here: https://agenticbooks.ai/contact
