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:
- An Axerity API key — full access to one organization's books and
files. Generated in
Settings → API keysand used as a Bearer token. - A system prompt that teaches your agent what Axerity is and what it's allowed to do.
- 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_transactionordelete_transaction, surface the args to the user and require an explicit OK. Treat it like a databaseBEGIN/COMMITyou 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_lockederrors. If your agent tries to post into a locked period, the API will reject with status423. 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.
testkeys hit the same backend aslive. 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.