FreeAgent API for Accounting Agents
FreeAgent lets you read accounting period locks before you write. An agent that skips that check will eventually post an entry into a period someone has already filed.
Accounting has a concept most APIs do not: the past closes. Once a period is locked, the numbers in it have been reconciled, signed off, and quite possibly filed with a tax authority. Changing them is not an edit, it is an amendment.
FreeAgent gives you a way to see this before you act. GET /v2/account_locks tells you which periods are locked. Any agent that writes anything dated should read that first.
The failure if you skip it is quiet and expensive. An agent processing a backlog of receipts sees one dated three months ago, posts it as an expense, and a period that reconciled last quarter no longer does. Nobody notices until someone reruns a report and the figure they presented to the board has moved.
Check the lock, then decide loudly
There are only two sensible behaviours when an agent wants to write into a locked period, and "write it anyway" is neither of them.
- Refuse and escalate. The agent stops, says which period is locked and what it wanted to post, and a human decides whether this needs an adjustment in the current period or a reopening of the old one. That is an accountant's call.
- Reclassify into the open period. Correct per the firm's policy, if it has one, and flag that the original date was outside the window. Still visible, still auditable, but it does not stall the queue.
Either way the agent's job is to make the situation visible, not to resolve it silently. In accounting, a failure that someone sees is cheap and a success that quietly moves a filed number is not.
Auth, limits, and a header that is genuinely thoughtful
FreeAgent uses OAuth 2.0 against https://api.freeagent.com/v2, with a full sandbox at https://api.sandbox.freeagent.com/v2. Use the sandbox. This is one of the clearest cases in this whole series where you want an agent's first hundred mistakes to happen somewhere that is not your real ledger.
A User-Agent header is required, so a bare request with no identification is rejected. Rate limits are 120 requests per minute and 3,600 per hour per access token, and exceeding either returns 429 with Retry-After.
The nice detail: FreeAgent offers an X-RateLimit-Test header so you can deliberately trigger rate limiting and check that your backoff actually works. Almost no API gives you this. Use it once while building, confirm your agent waits politely instead of hammering, and move on with real confidence rather than hope.
The hourly limit is the one that bites. A minute budget recovers quickly; 3,600 per hour means a full-history sync has a ceiling, so page deliberately and cache what does not change. Retries on 429 honour Retry-After automatically, which keeps a burst from turning into an hour-long lockout.
Setting it up
npx swytchcode
swy login
swy init --mode=sandbox # and point FreeAgent at its sandbox too
swy get freeagent
swy auth connect freeagent # OAuth; refresh handled for you
swy auth status
swy discover "list account locks" --project freeagent
swy discover "create an expense" --project freeagent
swy discover "list invoices" --project freeagent
swy list methods freeagent
swy info <canonical_id>
swy add method <canonical_id> # locks + reads first, then the one write you needPut the account locks read in the allowlist before anything else, because it is the method that makes the rest safe. Then reads for invoices, expenses, bank transactions and contacts. Then, narrowly, whichever single write the agent actually needs.
Leave out anything that deletes, anything touching bank account configuration, and anything that reopens a lock. An agent should never be the thing that unlocks a closed period, even in an emergency, because the emergency is exactly when nobody is checking.
OAuth tokens live in a local store outside your project, encrypted at rest, refreshed automatically and injected at call time so they never enter the model's context. No .env anywhere. See the authentication docs. Keep --project freeagent on discover, and run swy get before swy info or info reports a real method as missing.
Policy: money thresholds and the period guard
[
{
"id": "invoices-over-5k-need-approval",
"target": ["<freeagent create-invoice canonical_id>"],
"when": { "field": "total_value", "operator": ">", "value": 5000 },
"action": {
"type": "REQUIRES_APPROVAL",
"message": "Agent wants to raise an invoice above £5,000"
},
"approval_timeout": "4h"
},
{
"id": "no-backdated-writes",
"target": ["<freeagent create-expense canonical_id>"],
"when": { "field": "dated_on", "operator": "<", "value": "<current-period-start>" },
"action": {
"type": "REQUIRES_APPROVAL",
"message": "Entry is dated before the current accounting period"
},
"approval_timeout": "24h"
}
]The second rule is belt and braces on top of the lock check: even if the agent forgets to read the locks, a backdated write stops and asks. Held calls go to Slack or Telegram and fail closed after the timeout, so an unapproved entry is never posted. Setup is in the human approval docs, the schema in the policy reference, and because the file sits in your repo, raising a threshold is a reviewed pull request rather than a quiet afternoon decision. More in policy-as-code for AI agents.
Idempotency: the duplicate invoice
Of all the duplicate-write problems in this series, this is the one that reaches a customer. The invoice is created, the response is lost, the agent retries, and now a client has two invoices for the same work and a reasonable question about your competence.
Writes are only auto-retried when an idempotency key is set, so an unprotected invoice creation fails visibly rather than silently duplicating. Derive the key from something stable in your own system, such as the project and billing period, never from a timestamp, since a timestamp makes every retry look like a new invoice. Then have the agent search existing invoices for that reference before creating. Background in API idempotency: the complete guide and the idempotency docs.
And keep the audit trail. swy audit network records every call with its arguments and response, which in a finance context is not a nice-to-have but the thing you hand an auditor when they ask what the automation did. See how to log every API call an AI agent makes and API compliance for AI agent integrations.
Using it from Claude
Asking questions of your books in plain English is the best version of this. "Which invoices are more than thirty days overdue" through MCP in Claude Desktop beats building a report, and with a reads-only allowlist that session cannot post anything. Setup per client is in the MCP setup guide, the reference in the MCP docs, and skills.md hands a coding agent the commands to wire it up.
What you can build
- A receipt-to-expense pipeline with a period guard. Read receipts, extract supplier, amount and date, check the account locks, and either post to the open period or escalate. The lock check is what makes this safe enough to run unattended.
- Chasing overdue invoices. Read what is outstanding and how late, draft the follow-up in a tone matched to how late it is, and post the draft to Slack for a human to send. Read-only against FreeAgent, which is a nice property for something touching client relationships.
- Pre-close review. Before a period closes, read the transactions and flag the things humans catch late: uncategorised entries, a supplier whose spend jumped, an expense that looks like a duplicate. Entirely read-only and it makes close faster rather than riskier.
- A cash position briefing. Read bank transactions and open invoices and write a short weekly note on what is coming in and going out, into Notion. The agent never writes to the ledger at all.
FAQ
Should an agent write to the ledger at all?
For small, high-volume, well-shaped entries like categorised expenses, yes, with the lock check and a policy gate. For anything judgement-heavy or material, the agent should prepare and a person should post. The split is less about trust in the model and more about who answers for the number.
How do I test backoff without burning my rate limit?
Use the X-RateLimit-Test header against the sandbox. It is the single most underused feature in FreeAgent's API.
What if the write fails and I do not know whether it landed?
Search before retrying, always, in finance. And check the response body rather than trusting the status code, since the pattern in 200 OK with an error in the body is particularly unpleasant when the thing in question is money.
Does this work for an accountancy practice with many clients?
Yes, but keep one connection per client rather than one agent with access to all of them. Cross-client leakage in accounting is a confidentiality breach, and the cheapest way to prevent it is to make it structurally impossible.
Related reading
More content
SendGrid API for AI Agents
One boolean in the SendGrid payload mails people who unsubscribed. bypass_list_management is the field your agent must never be able to set.
Addepar API for Wealth Management Agents
Addepar has no read-only key tier, so a key carries its user's permissions. If you want an agent that can only read portfolios, you have to build that constraint yourself.
GitLab AI API and CI Agents
A personal access token gives your agent your permissions across every project you can see. A project access token gives it one project. That choice is the whole security model.
