Best Practices for Apps on the Beeswax API

The checklist for a production integration: access, correctness, resilience, data handling, testing and operations.

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 /meta when a token is entered and store account.id, account.name and scopes. 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: true says it is. Never sum, report or sync a document as revenue, expense, wages or a balance unless posted is true. state, number and 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 whose posted flips 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_groups sub-resources, not by re-posting the whole document; the header PATCH deliberately cannot touch lines so it can never drop them.

Resilience

  • Idempotency-Key on 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-After on 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 prefer updated_since to 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 = last server_time minus one minute, include_drafts=true for documents, upsert on id, store posted. 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, number and version_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: true or ledger: false are authored content, not postings.

Testing without a sandbox

There is no separate sandbox environment. Instead:

  1. 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.
  2. Create a token per developer in that account so revoking one never breaks a colleague.
  3. 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.
  4. 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: true invoices, and must not move when you create a draft.
  5. Before go-live on a real account, run in read-only mode first (a token with only :read scopes) 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-Agent such as acme-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-Id with every call and include it in support requests.
  • Watch for Idempotent-Replayed: true in 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 all unless you need the long-tail journal reader.
  • Put Idempotency-Key in 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 posted is 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 /meta on connect; required scopes verified; account name shown to the user
  • 401 handled as disconnection with a user-facing reconnect path
  • posted: true gates every aggregate and every ledger write to your side
  • Money parsed as decimal; dates kept as account-local dates
  • Idempotency-Key on every write; retries only on 409/429/5xx with the same key
  • Backoff with jitter; Retry-After honoured; per-token and per-IP limits budgeted
  • Unknown fields and enum values tolerated
  • Incremental sync with updated_since, one-minute overlap, upsert on id, include_drafts=true
  • Voids handled as reversals, not deletions
  • Tested against a free development account, including a read-only dry run
  • User-Agent set; X-Request-Id logged; alerts on 401 and stale last used
  • Changelog and status page bookmarked
Browse Topics