Sending a statement to FreeAgent
Connect FreeAgent once and send a converted bank statement straight into one of its bank accounts, instead of downloading a file and importing it by hand. What is sent, what is refused, and what happens when you send a period twice.
Connect FreeAgent once, and a bank statement in your list gets a FreeAgent button beside the CSV, Excel and OFX downloads. Pressing it uploads the transactions to a bank account you pick, in your own FreeAgent company.
This is an independent integration built on FreeAgent's public API. We are not affiliated with, endorsed by or supported by FreeAgent.
It is not a bank feed. Nothing here talks to your bank. A bank feed is your bank sending transactions to your accounting package on a schedule; this is a PDF you uploaded, read and checked here, and sent when you ask for it.
Connecting#
In the app, go to Settings → FreeAgent and press Connect. FreeAgent asks you to sign in and shows you its own screen describing what you are allowing.
We only ever write. Nothing in this product reads your invoices, your contacts, your bills or anything else out of your books.
Disconnecting deletes our copy of the permission. After that nothing here can reach your books and the FreeAgent button disappears. Statements you have already sent stay in FreeAgent, because they are your records now.
FreeAgent publishes no way for an application to withdraw its own access from their side, so to remove this app in FreeAgent as well, do it there under Settings. We would rather say that than have a button that claims to do something it cannot.
What gets sent#
The whole statement, in one upload, as an OFX file. OFX is the format FreeAgent's own documentation recommends for a statement upload, and it is the same file you would get from the OFX download button, so what the file contains is documented there in full.
Amounts are the exact figures the statement printed. They are decimal strings from the page to FreeAgent and nothing along the way turns one into a floating point number.
It goes as one upload rather than in pages on purpose, because of how FreeAgent matches transactions. See below.
Sending the same period twice#
FreeAgent decides this, not us, and its rule is worth knowing before you rely on it. From its own documentation:
For all statement imports, transactions are deduplicated against any existing transactions in the bank account on that date with the same amount and description.
So if you convert January, send it, then convert January to February and send that, the January transactions are matched against the ones already there rather than added again. That works whatever produced the originals, including a real bank feed.
The same rule cuts the other way, which is why we send the whole statement at once: two genuinely separate transactions on the same day, for the same amount, with the same description, can be read as one. FreeAgent's own advice is to "include all of a day's transactions in a single statement upload", and that is what happens here.
Always check the result against your books. After the upload we ask FreeAgent how many transactions it holds from it, and show you both numbers, because FreeAgent says of the upload's own success response that it "does not indicate whether or not your statement has been imported correctly". Fewer than were sent usually means it matched some against transactions it already had.
What is refused, and why#
A statement whose arithmetic disagrees with itself. If the running balance or the declared totals do not agree with the figures we read, the statement is marked with a mismatch and cannot be sent. That is stricter than the download, deliberately: because FreeAgent matches transactions on their amount, a corrected figure sent afterwards arrives as a second transaction rather than replacing the wrong one, and you would have to delete one by hand in FreeAgent.
Correct the row in the app first. The statement is checked again with your correction in place, and then it can be sent.
A statement nothing could check, unless you say so. Some statements print no running balance and no totals, so there is nothing on the page to check the figures against. Nothing found a problem with them; nothing confirmed them either. Because of the paragraph above, sending one of those takes one extra tick saying you want to.
An invoice or a receipt. FreeAgent's statement endpoint takes bank transactions, which have a direction: money in or money out. An invoice does not. The same invoice is money out to whoever received it and money in to whoever sent it, and the page never says which, so we do not put a sign on it anywhere in this product. Download it as CSV or Excel and enter it in FreeAgent as a bill or an invoice, which is where it belongs.
A bank account held in another currency. FreeAgent records a bank account's transactions in that account's own currency and nothing here converts between currencies, so a sterling statement is not offered a euro account.
A statement that holds three accounts#
A business account PDF can carry a euro account, a sterling account and a dollar account, printed one after another. There is no single send of that, because there is no FreeAgent bank account that holds all three.
There are three sends, and that is what the dialog asks for: which of the statement's accounts, then which FreeAgent account it goes to, and again for the next one. Each send carries one currency's transactions to one account, nothing is converted, and the statement shows which of its accounts have gone and which have not, so a job you come back to after lunch says where you left it.
One FreeAgent account never takes two of them. Where FreeAgent states an account's currency, an account in the wrong one is not offered at all; where it states none, an account that has already taken euros from here is refused sterling.
From the API#
Two endpoints, both needing a connection made in the app first: connecting is a browser redirect through FreeAgent's consent screen, which is not something a script can do on your behalf.
# Where a statement can go.
curl "https://extractbankstatements.com/api/v1/freeagent/bank-accounts" \
-H "Authorization: Bearer $KEY"
# Send one.
curl -X POST "https://extractbankstatements.com/api/v1/documents/$ID/freeagent" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"bank_account":"https://api.freeagent.com/v2/bank_accounts/1"}'
# One account of a statement that holds several, then again for the next.
curl -X POST "https://extractbankstatements.com/api/v1/documents/$ID/freeagent" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"bank_account":"https://api.freeagent.com/v2/bank_accounts/2","currency":"EUR"}'
The response carries transactions_sent, which is what we uploaded, and
transactions_in_freeagent, which is what FreeAgent reported afterwards. The
second is null when that read failed, which says nothing about what was
imported: check FreeAgent.
currency is the ISO 4217 code of the account to send, and it is required on a
statement holding more than one: without it that statement is refused with
currency_required, carrying currencies, which is the list to loop over.
transactions_sent and currency in the response are then about the account
that went, not the document.
Every refusal above is a 422 with a code naming which one it was, so a
script can tell "this statement contradicts itself" from "wrong currency"
without reading the sentence. A statement nothing could check is
verification_not_confirmed, and sending it needs "allow_not_verifiable": true. A contradicted statement is refused whatever you send.
There is deliberately no MCP tool for this. The MCP server is read only, and the first thing that writes should not be an irreversible push into somebody else's accounting system driven by a model.
Rate limits#
FreeAgent limits each of its own users to 120 requests a minute across every
integration they use, and answers a 429 with a Retry-After header. A send
costs three of those. If you meet that limit we pass it on as our own
rate_limited refusal with the same retry_after seconds, so a client that
already backs off does not need teaching a second shape.