Who this is for
Anyone building something that will run unattended against a real set of books: a two-way sync, a billing feature in your own product, a bookkeeping automation, an agent. It distils what has gone wrong for integrators of accounting APIs generally, and for Beeswax's own clients specifically, into a checklist. Tick every item before you point the integration at a customer's account.
Access
- Ask for a scoped token, never a password. Owners create tokens in Account Settings → API Tokens. Publish the exact scope list your app needs so they can grant it without guesswork, and explain what each write scope will let you do to their books.
- One token per Beeswax account per environment. Tokens are account-bound; a multi-client integration holds one per client account. Never share a production token with a staging deployment.
- Verify on connect. Call
GET /metawhen a token is entered and storeaccount.id,account.nameandscopes. Show the account name back to the user so they can see they connected the right one. Refuse to proceed if a required scope is missing, and say which. - Keep tokens server-side, encrypted at rest, in a secrets manager or your framework's encrypted credentials. Never in a repository, browser, mobile binary, spreadsheet or log line. Log the token's Beeswax name or your own id for it.
- Handle 401 as disconnection. The token has been revoked, has expired, the account's subscription has lapsed, or the person it belongs to has left the account. Stop calling, mark the connection broken, and tell the user how to reconnect. Do not retry in a loop.
- Rotate deliberately. Support entering a new token while the old one is still live, then confirm the old one's last used has stopped before the owner revokes it.
Correctness
- The ledger is what
posted: truesays it is. Never sum, report or sync a document as revenue, expense, wages or a balance unlesspostedis true.state,numberand the presence of lines are not signals. Read the ledger rule before writing a single aggregate. - Money is decimal. Parse amounts into a decimal type on arrival; never store them as floats; compare with the same precision Beeswax uses (two decimal places). When you send money, send two decimal places.
- Dates are account-local. Document dates (
sent_on,due_on,paid_on) are calendar dates in the account's time zone. Do not convert them through your server's zone, and do not send timestamps where a date is expected. - Every id must belong to the account. The API rejects foreign ids with a 422, but a good client never sends one: look up clients, projects, accounts and tax codes from the same account's reference endpoints and cache them per account.
- Nothing you do emails a client. Creating or finalising an invoice or quote does not send it. If your workflow needs the client to receive it, tell the user to send from Beeswax, or build the send into your own system.
- Ledger documents are voided, not deleted. Design your undo around
POST …/void, and treat a document whosepostedflips to false as a void to be reversed in your copy, not a row to delete. - Respect
editable. Check it before offering an edit; a paid or voided document will refuse writes. - Preserve document structure. Edit lines through the
transaction_groupssub-resources, not by re-posting the whole document; the headerPATCHdeliberately cannot touch lines so it can never drop them.
Resilience
Idempotency-Keyon every write, derived from your side's identity for the operation (payment:bank-line:<id>), never from the body alone. Retry 409, 429 and 5xx with the same key; treat 4xx as final.- Exponential backoff with jitter, capped, and always honour
Retry-Afteron a 429. - Budget for the rate limits: 300 requests a minute per token, and 300 requests per five minutes per IP address across everything you send to Beeswax from that address. Use
per_page=100, batch allocations into one payment, and preferupdated_sinceto full re-reads. - Tolerate additive change. Ignore unknown fields, accept unknown enumeration values as "other", and never rely on field order. New fields and endpoints arrive without a version bump.
- Time out and move on. A request that has not answered in 30 seconds should be abandoned and retried with the same key, not left to hang your worker.
Data handling
- Incremental sync is the default pattern. Poll each resource with
updated_since= lastserver_timeminus one minute,include_drafts=truefor documents, upsert onid, storeposted. There are no webhooks today; a five-minute poll of a quiet account is a handful of tiny requests. - Store Beeswax ids alongside your own, and keep the mapping even after a document is voided. Numbers can be reissued by the account's numbering rules; ids cannot.
- Keep a copy of what you wrote. When your app creates a document, store the returned
id,numberandversion_number(where present) so support can trace it, and so a replayed idempotent response can be recognised. - Minimise what you hold. Contacts carry personal data; pull only the fields you need, delete what you no longer need, and describe the flow in your own privacy documentation. Beeswax accounts may be in Australia, the UK or the EU, each with its own privacy regime.
- Do not fabricate ledger activity from previews. Lines returned with
preview: trueorledger: falseare authored content, not postings.
Testing without a sandbox
There is no separate sandbox environment. Instead:
- Sign up for a free Beeswax account at app.beeswaxapp.com and use it as your development tenant. It has the full API and the full 60-day trial of every feature; afterwards the Free plan keeps API access.
- Create a token per developer in that account so revoking one never breaks a colleague.
- Load fixtures through the API itself (clients, a chart of accounts is provided, products, a few invoices), or through the web app's CSV import.
- Write an integration test that runs your sync against the test account and asserts the ledger rule: your revenue total must equal the sum of
posted: trueinvoices, and must not move when you create a draft. - Before go-live on a real account, run in read-only mode first (a token with only
:readscopes) and diff what you would write against what is already there.
Never test against a customer's live account, and never share a test token with a production deployment.
Operations
- Identify yourself. Send a
User-Agentsuch asacme-billing/2.1 (+https://acme.example/beeswax). It lets us contact you before a change affects you and find your traffic when you report a problem. - Log
X-Request-Idwith every call and include it in support requests. - Watch for
Idempotent-Replayed: truein your logs; a spike means your retry logic is firing, which usually means a timeout or a 5xx upstream. - Alert on 401 and on a token's last used going stale; both mean the integration has silently stopped.
- Read the changelog when you deploy, and subscribe if you can. Behaviour changes are dated and list the endpoints affected.
- Check the status page before escalating a burst of 5xx responses.
Building an AI agent
If the consumer is a model rather than a program, three extra rules:
- Give it a read-mostly token: every read scope it needs plus only the specific write scopes the task requires. Never
allunless you need the long-tail journal reader. - Put
Idempotency-Keyin the tool layer, generated per tool call, so an agent re-issuing a call cannot double-post. The Beeswax MCP server does this for you. - Feed it the ledger rule as a system instruction: "an entry is on the ledger only when
postedis true; drafts, templates, quotes and voids are never activity". The MCP server ships this instruction; your own agent should too.
The one-page checklist
- Scoped token per account per environment, stored encrypted, never logged
GET /metaon connect; required scopes verified; account name shown to the user- 401 handled as disconnection with a user-facing reconnect path
posted: truegates every aggregate and every ledger write to your side- Money parsed as decimal; dates kept as account-local dates
Idempotency-Keyon every write; retries only on 409/429/5xx with the same key- Backoff with jitter;
Retry-Afterhonoured; per-token and per-IP limits budgeted - Unknown fields and enum values tolerated
- Incremental sync with
updated_since, one-minute overlap, upsert onid,include_drafts=true - Voids handled as reversals, not deletions
- Tested against a free development account, including a read-only dry run
User-Agentset;X-Request-Idlogged; alerts on 401 and stale last used- Changelog and status page bookmarked