FinanceAPIsAutomation

Accounting APIs lie politely

Three times a popular cloud accounts package has handed me numbers that were confidently, plausibly wrong. No errors, no warnings. Here are the traps and the defensive rules I now apply on day one.

I spend a lot of my time building finance automations and board reporting against a popular cloud accounts package. The API is decent as these things go: well documented, sensible authentication, valid JSON every time. It has also, three times now, handed me numbers that were confidently wrong. Nothing in the payload suggested a problem. Just polite, well-formed figures that did not mean what I thought they meant.

Each trap survived a first look, and each would have put a wrong number in front of a board if I had not checked the output against the package's own screens. Here they are, with the fixes.

Trap one: the monthly columns that are not monthly

The profit and loss endpoint takes a period count and a timeframe. Ask for twelve monthly columns and you get twelve columns, each neatly labelled with a month. The natural reading is that each column is that month's trading. It is not. Each column is period-to-date: cumulative from the start of the reporting period up to the end of that month.

Sum the columns, the obvious move if you want a year's revenue, and the total inflates five to ten times, because you are adding January to January-plus-February to January-through-March, and so on. The horrible part is that the result is a plausible-looking large number. It survived a glance from me, and it would survive a glance from most people in the room.

The fix is dull and reliable: one call per month, each with an explicit from date and to date. Twelve calls instead of one. Slower, and correct. I no longer trust a multi-column report from any accounting API until I have checked whether the columns are periods or running totals.

Trap two: the balance sheet that predicts the future

The balance sheet endpoint accepts any date you like and then quietly ignores it. Whatever you send gets snapped to a month end. Ask for the 14th and you get figures as at the 30th or 31st. That alone is survivable if you know about it. The dangerous part is what happens when the month end it snaps to sits ahead of the settled data: the endpoint returns forecast figures, and nothing in the response says so.

So a script that asks for "today" in the middle of a month can receive a prediction dressed up as fact. I caught it because a cash balance moved when nothing had happened in the bank. If the forecast had been closer to reality I would not have noticed, and a projected number would have gone into a board pack as an actual.

The fix: never ask for today. Request the last settled month end, explicitly, every time, and label every figure with the date it actually represents, not the date you wanted.

Trap three: the token refresh that logs everyone out

This one is not about the numbers, it is about the plumbing underneath them. The package uses OAuth with single-use refresh tokens. Refresh a token and the old one is dead; whoever holds the new one owns the connection.

I had a production app holding that connection, and an ad-hoc script running a one-off report. The script refreshed the token and wrote the new one back to its own copy of the credentials. Now two things believed they owned the token, the production app refreshed with a dead credential, and the whole integration disconnected. At 9pm. Re-authorising meant finding the one person with admin access to the accounts package, that evening.

The rule I now follow without exception: one owner per token. Ad-hoc scripts read with the current access token and stop. They never refresh and write back unless they are the single component that owns the token store. If the access token has expired, that is the script's problem, not a licence to grab a new one.

The shape of the problem

The meta-lesson took me longer to see than any single trap. Accounting APIs are not databases with an HTTP wrapper. They are report generators. An endpoint called profit and loss is not a table of transactions, it is a presentation layer with an accountant's conventions baked in: period-to-date columns, month-end snapping, forecast fill for unsettled periods. Every one of those conventions is reasonable to an accountant and invisible to an engineer.

Which means every number you show a board needs its definition checked against the ledger, not inferred from the endpoint name. "Revenue" from a reports endpoint is whatever that report means by revenue, under whatever conventions it applies to the dates you gave it.

The defensive rules

The short version, applied to any accounting integration on day one:

  • Never sum report columns. Request each period with explicit date ranges, and reconcile one month against the on-screen report before automating the rest.
  • Never request a balance sheet for a date newer than settled data. Ask for the last settled month end and label the figure with that date.
  • Treat any figure dated in the current month as a forecast until proven otherwise.
  • One owner per OAuth token. Read-only scripts never write refreshed tokens back.
  • Reconcile every automated figure against the package's own reports before it reaches anyone senior. The endpoint is not the ledger.

None of this is clever. All of it is cheaper than explaining to a board why last quarter's revenue just shrank by a factor of seven.

Building or running AI in a real business? Let's talk.

← All writing