maesn
For developers

How to integrate with BuchhaltungsButler: Four rate limits on one interface

BuchhaltungsButler has a small surface and an unusual shape: 48 operations, every one of them a POST, and the meaning carried by the path rather than the verb. None of that is what constrains your build. Four published limits do, three of them sit on the write paths this product exists for, and nothing here sends an event to spare you the calls.

Lennart Svensson, CTO and Co-Founder at Maesn
Lennart Svensson
CTO and Co-Founder · · Updated
Illustration for How to Integrate with BuchhaltungsButler: Four Rate Limits
The context

Two values authenticate you, the third picks the customer

Connecting takes three values out of your customer’s account: an API Client, an API Secret and an API Key. Almost every description of this system, ours included, calls them three credentials and moves on. The reference does not group them that way, and the difference decides how your multi-account code is shaped.

Under the heading Authentication, the reference is specific about two of them: “Authentication is done by HTTP Basic Auth as specified by RFC 2617. This is to be implemented so that it produces the ‘Authorization’ request header, containing the following value: ‘<Api Client>:<Api Secret>’.” That is the credential pair, and it travels in the header on every request.

The third value gets its own heading, Customer Selection, and a different job: “In order to use the API methods you need to specify the BB customer account you want to manage. This is done by providing the form field ‘api_key’ which contains the target customers api key.”

The API Key does not authenticate anything. It names whose books you are touching, and it travels as a form field on the request rather than in a header.

For a script that serves one customer account that distinction is trivia. For a product with a thousand connected companies it decides the shape of your code: selection does not ride in the header with the credentials, it is payload that has to be correct on every single request. Get it wrong and you do not get a 401, you get someone else’s books.

The two layers in one requestHTTP
POST /api/v1/receipts/get HTTP/1.1
Host: app.buchhaltungsbutler.de
Authorization: Basic BASE64(api_client:api_secret)
Content-Type: application/json
 
{ "api_key": "<the customer's key>" }

The header carries the credential pair, the body carries the selection. The api_key names the customer account and changes on every request that serves a different one.

Both quotations from BuchhaltungsButler’s published reference, sections Authentication and Customer Selection, checked 19 August 2026. The reference also says where the value comes from: the api key “can be found in customers company data settings if customer has registered to api access”.

There is no OAuth redirect anywhere in this, which is why BuchhaltungsButler is one of the seven systems that can be connected without a hosted page at all. Your customer activates access in their own settings, all three values appear together, and they can be submitted from inside your own interface.

Unified authentication is what keeps that from becoming a per-system branch: a redirect on the next system and a database credential on the one after arrive at the same account key, and your code only ever sees the key.

The one step you cannot take for your customer
Until someone clicks API-Zugang aktivieren in their own account, there is nothing to connect to and no values to copy. It belongs in your onboarding screen as an instruction, not in your backlog as a task.
The problem

Every one of the 48 operations is a POST

Counted from the published reference: 48 distinct operations, and all 48 are POST. That includes the twelve whose path carries a get segment, the two that delete and the four that update. The HTTP method is not a description of what happens here. The path is.

It is easy to file this under cosmetic and wrong to do so. A normal HTTP client derives three things from the verb without being told: whether a call is safe to repeat, whether a response can be cached and whether a failed request can be retried blindly.

On an interface where reading and deleting are both POST, none of those inferences hold, and every one of them has to be decided per path instead.

What your client expects48 of 48 are POST
GET /receipts
POST /receipts/get

Twelve of the 48 paths carry a get segment, and every one of them is reached with a POST.

DELETE /receipts/:id
POST /receipts/delete/id_by_customer

Two paths delete. Both are POST, and both mark rather than remove.

PATCH /debtors/:id
POST /settings/update/debtor

Four paths update, all POST, and the parties sit behind settings.

The method stops being a signal. Safety, caching and retry behaviour cannot be read off the verb here, so they have to be decided per path instead of per request.

What a client expects on the left, what the reference publishes on the right. Counted 13 August 2026.

The operations are not spread evenly either, and the distribution says what the product is for. Two tags carry eleven operations each, and one of them is not an accounting object at all but the settings surface where the parties live.

The 48 operations by tag, as the reference groups them
TagOpsWhat it covers
Postings11Add, batch, unconfirm
Settings11Debtors, creditors, accounts
Receipts8Upload, assign, delete, restore
Transactions8Read and match to receipts
Cost locations4The only full round trip
Invoices3Create, draft, e-invoice
Accounts2Read and add
Comments1Add

Tags and counts taken from the operation list in the vendor reference, checked 19 August 2026. Cost locations is the only tag carrying add, get, update and delete together.

The problem

Four limits, and the tightest sit on the write paths

This is the part that decides your architecture, and it is the part that neither our own system page nor the article it replaces mentions. The reference publishes a ceiling in one sentence: “It is only allowed to make a maximum of 100 requests per customer per minute to the API.” Per customer, not per application, which is the good news: your hundredth customer account does not slow down your first.

The other three are easier to miss because they are not in the limits section at all. They sit as implementation notes on individual operations, and counted across the reference there are exactly three of the 48: /receipts/upload carries “Note: max 10 requests per minute”, while /receipts/addBatch and /transactions/addBatch each carry “Note: a request is allowed only every 5 seconds”.

Put those facts next to what this product does and the shape of the constraint appears. BuchhaltungsButler exists to take receipts in and turn them into bookings, and every operation that writes at volume is capped below the global ceiling: the single upload at a tenth of it, and the two batch paths at twelve requests a minute each. The 100 per minute is the budget for reading, writing has its own and much lower one.

A migration of a customer’s back catalogue is therefore not bounded by your worker count or their plan. It is bounded by ten uploads a minute, or by one batch every five seconds if you go that way, and no amount of parallelism moves either number.

One customer, one minuteFour separate ceilings
Every operation, per customer
100 / minute
The ceiling the reference states for the interface as a whole.
Receipt upload, per minute
10 / minute
Its own note on /receipts/upload.
Receipts, batch endpoint
1 / 5 seconds
Its own note on /receipts/addBatch.
Transactions, batch endpoint
1 / 5 seconds
Its own note on /transactions/addBatch.

The poll comes out of the same allowance. No object here announces its own changes, so every check for what moved is a request you spend, and the three write endpoints each have their own floor to stay under while you spend it.

One customer, one minute. The endpoint ceilings are separate limits rather than a share of the global one. All four published, checked 19 August 2026.

Global ceiling from the API limits section of the vendor reference; the three endpoint notes from the implementation notes of those operations, checked 19 August 2026. The reference does not document a status code for exceeding any of them, so read the response and do not assume it.

Budget the backfill before you promise it
Ten uploads a minute is 600 an hour. A customer arriving with two years of receipts is a scheduled job measured in days, and that is a number to say out loud during onboarding rather than discover in week three.
How it works

Customers and suppliers are debtors and creditors in settings

There is no contact object here, and there is no customer object either. The parties exist as debtors and creditors, and they are not filed with the documents that reference them. All eight operations that read, create and update a party sit behind a settings path.

That is a bookkeeping view rather than a product view, and it is internally consistent: in a system whose job is classifying documents, a debtor is a piece of configuration that decides where a receipt lands. It is simply not the view your product has, where a customer is a first-class thing with a name and an address.

It is also the clearest case on this system for reading through a normalised layer. The common data model gives you customers and suppliers as objects with the same fields you use on every other connected system, and the fact that one vendor keeps them under settings and another under contacts stops being something your code knows about.

Creating a party, as the reference names itHTTP
POST /api/v1/settings/add/debtor
POST /api/v1/settings/add/creditor
POST /api/v1/settings/add-batch/debtors
POST /api/v1/settings/update/debtor

Four of the eight party operations. The other four read them back and update creditors, and every one of them keeps the settings prefix.

Paths from the Settings tag of the vendor reference, checked 19 August 2026. Through Maesn the same records read as customers and suppliers, which are the two objects here that make the full round trip of read, create and update.

How it works

A deleted receipt is a flag, and a fixed posting stays put

Two behaviours here will surprise anyone who assumes a document store. Neither is mysterious once you read the operation notes, and both change what your code has to tolerate.

The first is that deletion does not delete. The reference describes the operation as “Mark a receipt as deleted for a specified customer account”, and there is a matching operation to “Restore a marked as deleted receipt”. A removed document is a state, and anything you write that reads without accounting for that state will keep seeing it.

The second is that bookings can become immovable. All three unconfirm operations, for free postings, receipt postings and transaction postings, carry the same sentence: “This will only work if the postings are not fixed.” Once a posting is fixed, the path back is a new posting rather than an undo, which is ordinary accounting behaviour and unusual API behaviour.

There is a third habit worth planning for, because it costs a request. Six operations identify their target by id_by_customer, and the reference tells you where to get it: “You can get the ‘id_by_customer’ by using the ‘/receipts/get method’ first.” A delete is therefore two calls, and both of them come out of the same hundred.

A delete is a read and then a writeHTTP
POST /api/v1/receipts/get
-> id_by_customer
 
POST /api/v1/receipts/delete/id_by_customer
-> marked as deleted, restorable

Two requests against the per-customer allowance for one logical operation, and the second one is reversible by design.

Quotations from the implementation notes of the receipt and posting operations in the vendor reference, checked 19 August 2026. The reference does not define when a posting becomes fixed, so handle the condition instead of predicting it.

How it works

One scheduled read, and it shares your request budget

BuchhaltungsButler does not announce its own changes, so there is no subscription to configure and no callback to verify. Staying current is a scheduled read. That is a property of the system and not a setting, so it belongs in the plan from the start.

What takes its place is a delta pull on the same filters and the same paging you use everywhere else: you ask what changed since your last run and you get it back in the shared model. In the event model that is the pull half doing the work, and on this system the push half has nothing to fill it.

A pull has one property a push does not, and on this system it is the relevant one. It catches up. If your worker is down for an hour, the next run still returns everything that moved in that hour, and on a platform where documents arrive in batches from a scanning app that matters more than latency does.

What it costs is requests, and requests are the thing that is capped. Every poll is spent from the same hundred per customer per minute as your writes, so the interval is a budget decision rather than a preference. Reading rarely and asking for a wide window is cheaper than reading often and asking for a narrow one.

Division of labour

What Maesn covers, and what stays with you

The connection here is short work, which is exactly why the rest deserves attention. What we take off the list:

  • The verb translation. Your client uses the ordinary REST verbs and the mapping onto 48 POST paths lives on our side, so the two inferences an HTTP client normally makes stay available to you.
  • The two credential layers. The Basic Auth pair and the per-customer key are stored with the connection, so the account selector stops being something your request builder can get wrong. How the party model maps is on the BuchhaltungsButler API page.
  • The party model. Debtors and creditors under settings arrive as customers and suppliers, with the fields they have on every other system.

What stays with you, and the first of these is a decision rather than a task:

  • How you spend the hundred. We do not turn this into an event source, because it is not one. How fresh your product needs to be, and what that costs against a per-customer ceiling, is a product decision we can inform and not replace.
  • The pace of a backfill. Ten uploads a minute is the vendor’s number, and no interface in front of it makes the eleventh upload succeed.
  • The net amount, if you need one. This is one of three connected systems that carry a single gross amount field, with net and tax empty. Deriving a net basis is arithmetic on your side or a question for a different source.
  • The click in your customer’s account. Activating access and copying three values is theirs to do, and saying so clearly in your own interface is the difference between a connection and a support ticket.
FAQ

Frequently asked questions

What are the BuchhaltungsButler rate limits?

Four. The reference sets a global ceiling of 100 requests per customer per minute, and three of the 48 operations carry a note of their own: the receipt upload at ten per minute, and both batch endpoints at one request every five seconds. The endpoint limits are not a share of the global one, they are separate caps on the write paths, so bulk imports need pacing rather than parallelism.

Why does a read request have to be a POST?

Because the verb is not where the operation lives. All 48 published operations are POST, and twelve of them carry a path segment that reads get. The meaning sits in the path, so your HTTP layer cannot infer safety, caching or retry behaviour from the method the way it normally would.

What is the difference between the API Client, the API Secret and the API Key?

The first two are the credential pair for HTTP Basic Auth. The third does not authenticate anything: it names the customer account you want to act on and travels as a form field on the request. Across many connected customer accounts that makes it per-call data rather than connection setup.

Does deleting a receipt actually remove it?

No. The operation marks the receipt as deleted, and a separate restore operation brings it back by the same identifier. Treat it as a state change rather than a removal, and expect a deleted record to keep appearing in anything that reads without filtering on that state.

Why can I not unconfirm a posting?

Because it has been fixed. The reference repeats the same condition on all three unconfirm operations, for free postings, receipt postings and transaction postings: it only works if the postings are not fixed. Once that state is set, the correction path is a new posting rather than a reversal.

Where do customers and suppliers live in BuchhaltungsButler?

Under settings. There is no combined contact object, and the eight operations that read, create and update parties all sit behind a settings path as debtors and creditors. Through Maesn they arrive as customers and suppliers with the fields you use on every other connected system.

Can I get a net amount from a BuchhaltungsButler invoice?

Not from the document itself. This is one of three connected systems that carry a single amount field, and that field is gross. Net and tax come back empty, so anything that has to settle on a net basis needs its own calculation or a different source for the tax split.

Build once on the Unified API.

BuchhaltungsButler meters ten uploads a minute, sevdesk publishes no limit at all and Xero meters what you read. Build against one interface and each of those becomes a row in a table rather than a project.