Axerity Docs
Agents

Bring your own agent

Wire your own LLM agent — Claude, GPT, Hermes, anything — against the Axerity REST API. Drop in a prompt, drop in an API key, you're done.

The in-app Books AI is convenient, but it's not the only way to drive Axerity from an LLM. If you already have an agent framework — Claude with tool use, GPT with function calling, Hermes, LangChain, your own custom loop — you can point it straight at our REST API and skip the dashboard entirely.

This page is the recipe.

The shape of the integration

Three pieces, in this order:

  1. An Axerity API key — full access to one organization's books and files. Generated in Settings → API keys and used as a Bearer token.
  2. A system prompt that teaches your agent what Axerity is and what it's allowed to do.
  3. Tool definitions in whatever format your model expects (OpenAI function spec, Anthropic tool spec, JSON Schema, etc.) — each one wraps one of our REST endpoints.

Your agent reasons; your agent calls a tool; your tool implementation makes an HTTP request to https://axerity.com/api/v1/... with the Bearer token. We do the rest.

1. Mint a key

Follow API → Authentication. Two things to remember:

  • Keys are per-org. If your agent operates across multiple orgs, mint one key per org and route by context.
  • Plaintext is shown once. Put it in a secret manager — never in your prompt, repo, or chat logs.

For development, mint a test key against a sandbox org you don't mind breaking. Save live keys for production agents you've verified.

2. The system prompt

Here's a tested template. Drop your org-specific context into the bracketed sections — chart of accounts, currency, fiscal year, anything your agent should know up front.

You are a bookkeeping agent for a small business using Axerity Books.

## Tools you have

You can call these tools to read and write the books. They are thin
wrappers over the Axerity REST API. Every write hits the live ledger —
there is no preview or sandbox unless the user has explicitly given you
a `test` key.

- list_accounts(include_archived?): chart of accounts
- list_transactions(account_id?, q?, cursor?, limit?): paginated journal
  entries, optionally filtered or searched
- get_transaction(id)
- create_transaction(date, memo?, reference?, lines): post a new entry
- delete_transaction(id): permanently delete (audited)
- income_statement(from, to)
- balance_sheet(as_of)
- trial_balance(as_of)
- list_files(folder_id?)
- list_folders(parent_id?)

## Hard rules

1. Money is **minor units (cents) as strings** — "10000" means $100.00,
   never 100.00 or 100. Read every amount from a tool result; don't do
   currency math in floating point.
2. Every transaction has at least two lines. Each line has either a
   `debit` or a `credit` but not both. Sum of debits MUST equal sum of
   credits.
3. Dates are `YYYY-MM-DD`. If a period is locked, posting into it will
   fail with `period_locked` — surface that error verbatim.
4. Never invent account ids. Always list accounts first and pick from
   the response.
5. Before any `delete_transaction` call, fetch the entry with
   `get_transaction` and show the user what's being deleted.

## Context

- Organization: [your org name]
- Base currency: [USD / EUR / etc.]
- Fiscal year start: [Jan 1 / Apr 1 / etc.]
- Chart of accounts: [paste the output of list_accounts here, or fetch
  it at session start]

A few notes on what's in there:

  • The minor-units rule is the single most common source of bugs. Make it loud.
  • The "never invent account ids" rule is what keeps agents from hallucinating "acct_cash_1234" when no such id exists.
  • The "delete confirmation" rule is your version of our in-app confirmation card — you're recreating it in prompt form because we can't enforce it from the API side.

3. Tool definitions

Here are the most useful endpoints, expressed as OpenAI / Anthropic- style tool definitions. Paste these into your agent runtime and implement each execute as a fetch call with the API key.

[
  {
    "name": "list_accounts",
    "description": "List accounts in the chart of accounts. Each account has id, code, name, type (ASSET/LIABILITY/EQUITY/REVENUE/EXPENSE), and archived.",
    "input_schema": {
      "type": "object",
      "properties": {
        "include_archived": { "type": "boolean", "default": false }
      }
    }
  },
  {
    "name": "list_transactions",
    "description": "List journal entries, newest first. Cursor-paginated. Use `q` for free-text search across memo, reference, and line memos.",
    "input_schema": {
      "type": "object",
      "properties": {
        "account_id": { "type": "string" },
        "q": { "type": "string" },
        "cursor": { "type": "string" },
        "limit": { "type": "integer", "minimum": 1, "maximum": 200 }
      }
    }
  },
  {
    "name": "get_transaction",
    "description": "Fetch a single journal entry with all lines.",
    "input_schema": {
      "type": "object",
      "properties": { "id": { "type": "string" } },
      "required": ["id"]
    }
  },
  {
    "name": "create_transaction",
    "description": "Post a new balanced journal entry. Amounts are minor-unit strings (e.g. \"12345\" for $123.45). Each line has either debit OR credit, not both. Sum of debits must equal sum of credits.",
    "input_schema": {
      "type": "object",
      "properties": {
        "date": { "type": "string", "description": "YYYY-MM-DD" },
        "memo": { "type": "string" },
        "reference": { "type": "string" },
        "lines": {
          "type": "array",
          "minItems": 2,
          "items": {
            "type": "object",
            "properties": {
              "account_id": { "type": "string" },
              "debit": { "type": "string" },
              "credit": { "type": "string" },
              "memo": { "type": "string" }
            },
            "required": ["account_id"]
          }
        }
      },
      "required": ["date", "lines"]
    }
  },
  {
    "name": "delete_transaction",
    "description": "Permanently delete a journal entry. The deletion is recorded in the audit log but the entry is gone.",
    "input_schema": {
      "type": "object",
      "properties": { "id": { "type": "string" } },
      "required": ["id"]
    }
  },
  {
    "name": "income_statement",
    "description": "Revenue and expenses over a period. Amounts in minor units.",
    "input_schema": {
      "type": "object",
      "properties": {
        "from": { "type": "string" },
        "to": { "type": "string" }
      },
      "required": ["from", "to"]
    }
  },
  {
    "name": "balance_sheet",
    "description": "Assets, liabilities, equity at a point in time.",
    "input_schema": {
      "type": "object",
      "properties": { "as_of": { "type": "string" } },
      "required": ["as_of"]
    }
  }
]

A minimal execute for list_accounts in TypeScript:

async function list_accounts(args: { include_archived?: boolean }) {
  const url = new URL("https://axerity.com/api/v1/accounts");
  if (args.include_archived) url.searchParams.set("include_archived", "true");

  const res = await fetch(url, {
    headers: {
      authorization: `Bearer ${process.env.AXERITY_API_KEY}`,
    },
  });
  if (!res.ok) throw new Error(`Axerity ${res.status}: ${await res.text()}`);
  return res.json();
}

The other endpoints follow the same pattern. See the full API reference for the exact paths.

Safety: what's different from the in-app agent

The biggest difference between Books AI and your own agent is that your agent has no confirmation card layer. When it calls create_transaction, the entry is created. When it calls delete_transaction, the entry is deleted. There is no Post button.

That means all the safety has to live in your prompt and your runtime, not in our UI. We strongly recommend:

  • Run dry against a test org first. Mint a key for a throwaway org and exercise your agent there until you trust it.
  • Add a human-in-the-loop for writes. In your runtime, before executing create_transaction or delete_transaction, surface the args to the user and require an explicit OK. Treat it like a database BEGIN/COMMIT you don't auto-commit.
  • Use the audit log. Every write is recorded with the actor's API key id, timestamp, and a full before/after snapshot. Tail it during agent development.
  • Rotate keys aggressively. When you finish a development session, revoke the key. Re-mint for production with the smallest set of responsibilities you can.
  • Watch for period_locked errors. If your agent tries to post into a locked period, the API will reject with status 423. Surface the message — don't let the agent retry blindly.

Idempotency and retries

We don't yet support idempotency keys on writes. If your agent's runtime retries a create_transaction after a timeout, you can post the same entry twice. Until idempotency lands, don't retry write endpoints on ambiguous failures — instead, query list_transactions to confirm whether the prior call landed.

Read endpoints (list_*, get_*, reports) are always safe to retry.

What's not yet possible

  • Webhooks. No outbound notifications when entries are created or deleted. Your agent has to poll or be the source of writes.
  • Multi-org keys. Each key is one org. Multi-tenant agents need to manage their own key map.
  • Scoped keys. All keys are full-access today. Read-only or write-only scopes are on the roadmap.
  • Test sandbox. test keys hit the same backend as live. Use a throwaway org until isolated sandboxes ship.

A working example

A complete reference agent — Claude with tool use, the prompt above, and the seven tools wired — fits in about 200 lines. We're working on publishing it as a starter repo; in the meantime, the building blocks on this page are everything you need.

Questions or wanting an SDK in a particular language? Open an issue or ping us — we'd rather hear what's missing than guess.

On this page