maesn
For developers

How to integrate with Lexware Office: Two requests per second and no polling

Lexware Office is not a hard system to call. It is a hard system to call often. Three documented rules decide your architecture before you write a line: the rate limit covers every endpoint at once, every write needs a read first and Lexware asks you not to poll.

Lennart Svensson, CTO and Co-Founder at Maesn
Lennart Svensson
CTO and Co-Founder · · Updated
Illustration for How to Integrate with Lexware Office: Limits and Events
The context

Why Lexware Office decides German SMB coverage

Lexware Office is where a large share of German small businesses, freelancers and their tax advisors keep their books. If your product sells into that market, covering it is often the difference between shipping in Germany and explaining why you cannot.

What makes it demanding is not the protocol. It is a REST API, and reaching a single record is unremarkable. The difficulty is that German accounting law sits inside the data model, and that the platform protects itself with rules that shape your architecture rather than your request bodies.

This guide is about those rules. If you want the object coverage, the authentication options and the marketplace levels in one place, that is the Lexware Office API page. What follows is how to build against the platform without running into the three constraints that catch most first implementations.

The problem

Who can connect and on which plan

Two things decide whether your customer can connect at all, and both sit outside your code: the plan they are on, and your own relationship with Lexware.

The route open to everyone is the public API, authenticated with an API key. Lexware states that it is available from version XL upward, so a customer on a smaller plan has no key to give you.

The key is created inside Lexware Office by a user with administrator rights, on the public API add-ons page. It belongs to that one account, so each of your customers goes through it once for their own environment.

There is a second route, and it opens through a partnership with Lexware. That is a manual qualification with their partner team. It reaches a wider set of endpoints and operations, it authenticates differently, and it runs under a higher rate limit.

Maesn supports both routes, the endpoints as well as the authentication, so your implementation does not change with the one your customer ends up on. We run the qualification with you rather than handing you a form.

Lexware publishes the programme itself, including the benefits that come with it beyond API access. In practice the two that matter commercially are co-marketing and a listing in the Lexware marketplace. Maesn has been through it: Lexware lists us as a partner on their own site, which is the only kind of proof for a claim like that which does not come from us.

Plan requirement and programme from Lexware’s own public API partner page, checked 4 August 2026.

What this means for your onboarding

The plan check belongs in your connect flow, not in your support inbox. A customer who cannot produce a key will read a failed connection as your bug. Say which plan is needed before they try, and say what the alternative route is.

The problem

Two requests per second, across every endpoint

A client may send two requests per second. The important half of that sentence is the scope: Lexware documents that the limit refers to all endpoints at the same time. It is one budget for your whole integration, not one per resource.

That budget belongs to the public route. Going through a partnership raises it, which is worth knowing if volume is the reason you are reading this section at all.

The mechanism is a token bucket, and exceeding it returns 429 without performing the call. Lexware recommends running a token bucket on your side too, or at minimum a sleep between consecutive calls with exponential backoff on 429. They also warn that network jitter alone will trip a client that enforces the limit exactly, so plan below it rather than at it.

The authorization server has its own limits, and those are not published. The consequence there is harsher than a rejected call: a client that keeps sending gets blocked for seconds to minutes, and one that does not slow down stays blocked until it does.

Retry behaviour, backoff and the difference between a rejected call and a blocked client are the same problem on every system we support, which is why it sits in unified error handling rather than in each integration.

One second, every endpoint2 requests
1GET /contacts/{id}

read the current version

2PUT /contacts/{id}

send it back with that version

One update fills the second. Every other call, including the next update, waits for the next one.

The whole budget is two requests per second, and it covers every endpoint at the same time. One update spends both.
Two limits, two failure modes
LayerLimitOn exceeding
Resource endpoints2 requests per second, all endpoints together429, the call is not performed
Authorization serverseparate, not publicly documented429 plus a temporary block that becomes permanent while the rate stays high

Both rows from Lexware’s API documentation, section API Rate Limits, checked 4 August 2026.

The problem

Every update starts with a read

Lexware uses optimistic locking, so a record is never locked before you modify it. Instead every PUT has to carry the version you received from your last GET of that record. If something else changed it in between, your version is stale and the write is refused.

version is read-only and increases on every change. You cannot compute it, and you cannot cache it for long, which is the part that costs you.

Read the version, then write it backHTTP
GET /v1/contacts/{id}
→ 200 { "id": "f5d5e4c2…", "version": 2, … }
 
PUT /v1/contacts/{id}
{ "version": 2, … }
→ 200 { "id": "f5d5e4c2…", "version": 3, … }
 
PUT /v1/contacts/{id}
{ "version": 2, … } # someone else already wrote version 3
→ 409 Conflict

Field semantics from Lexware’s documentation, section Optimistic Locking. Endpoint paths shortened for readability.

The arithmetic that decides your design

One update is two calls, and your budget is two calls per second for everything. So a naive loop over records manages roughly one update per second and leaves nothing for reads, webhooks or anything else your integration is doing at the same time.

That is not a benchmark we ran, it is the two documented rules above put next to each other. It is also why bulk work against Lexware belongs in a queue with a rate governor, not in a request handler.

How Maesn solves it

Polling is not allowed, so the sync runs on events

Lexware is explicit about this. Their cookbook says polling resources is to be avoided and names webhooks as the way to keep two systems in step. Combined with the budget above, that is less a recommendation than a design constraint: there is no rate at which polling both stays inside the limit and keeps data fresh.

Natively, Lexware sends an event such as invoice.status.changed to a callback URL you registered, with the account, the resource id and the time of the change. Delivery failures are retried over 48 hours, and their own guidance is to accept the call into a queue and process it asynchronously rather than doing the work in the handler.

Through Maesn you subscribe per customer to a resource and an event type, and you receive the same shape you receive for every other system. That is what unified webhooks means in practice: one payload, one subscription call, whatever the platform underneath emits.

Subscribe for one customer, then handle one shapeJS
await axios.post(url, {
callbackUrl,
eventType: "CREATED",
resource: "CUSTOMER"
}, { headers: { "X-API-KEY": apiKey, "X-ACCOUNT-KEY": accountKey } });
 
// what arrives at callbackUrl
{
"eventType": "CREATED",
"filterDate": null, // not supported by Lexware
"resource": "CUSTOMER",
"resourceId": "1605408d-ed88-4228-8a12-ab857a2972d8",
"userId": null // not supported by Lexware
}

Request and payload shape verbatim from the Maesn Lexware Office documentation.

Lexware sends
eventType
invoice.status.changed
organizationId
aa93e8a8…
resourceId
4d43ad14…
eventDate
2023-05-23T12:30:00.000+02:00
Maesn delivers
resource
INVOICE
eventType
UPDATED
resourceId
1605408d…
filterDate
nullnot sent by Lexware
userId
nullnot sent by Lexware
The same change, in Lexware's shape and in the unified one. Two fields stay empty because Lexware does not send them, and the documentation says so rather than hiding it.

Two things stay yours. The subscription is created per customer, so a new connected account means a new subscription, and filterDate and userId arrive empty for Lexware. Unifying a payload does not invent data the source never sent, and an integration that branches on those two fields will branch wrongly here.

How Maesn solves it

Keys expire after 24 months, and revocation arrives as an event

An API key is not permanent. Lexware sets its lifetime to 24 months and renews it in the same key management screen where it was created. A connection that has worked for two years is therefore a connection that is about to stop working.

A customer can also end it earlier, and there is a signal for that. Maesn emits a TOKEN resource with the event type REVOKED when a customer withdraws access or the token can no longer be refreshed. Handling it is the difference between telling the customer their connection is gone and finding out from their support ticket.

One more thing worth building for: a customer may create several keys and may restrict what a key is allowed to reach. If they restrict it below what you need, your calls fail with 403 and not 401. The credential is valid, the permission is not, so tell them which scope you need at the moment they create the key. How that folds into one flow across systems is unified authentication.

Key lifetime and multiple keys from Lexware’s public API cookbook, checked 4 August 2026. The 403 semantics are from their HTTP status code table, the TOKEN event from the Maesn documentation.

What you get

What German law puts in your data model

Some fields are not optional, and they are mandatory for legal reasons rather than technical ones. Three of them will reach your own model whether you planned for them or not.

FieldWhat it isThe catch
currencyCurrency of a price and of a line totalDocumented as EUR only. Convert on your side and keep the original amount and rate
xRechnung.buyerReferenceThe Leitweg-ID for invoices to German public sector buyersIf it is set, vendorNumberAtCustomer has to be set as well
electronicDocumentProfileNONE, EN16931 (ZUGFeRD) or XRechnungRead-only, so it tells you what a document is and not what you want it to be

All three from Lexware’s API documentation, contact and line item properties, checked 4 August 2026.

There is a second layer to what a given customer can actually do, and it is easy to get wrong: the features their Lexware contract grants are documented as orthogonal to the permissions on the key, and they change over time. Lexware therefore exposes the available set on the profile endpoint. Read it instead of assuming it.

Where our part ends

What stays on your side: Taxes, currency and the plan

We absorb the shape of the integration, not the rules of the platform. Five things stay with you, and it is cheaper to know that now than after the first customer call.

  • Tax is not calculated by Maesn. We deliver the rates and structures as the system publishes them. What you owe, and on which basis, stays your model.
  • Currency conversion is yours. Lexware supports EUR only, so the converted value and the rate you used are decisions on your side.
  • The plan requirement is Lexware’s. We can tell you it exists and where it applies. We cannot lift it for a customer who is not on it.
  • The partner qualification is Lexware’s decision. We prepare it and walk through it with you, and we do not grant it.
  • Per-customer subscriptions stay per customer. One connected account, one subscription. That is the platform’s model and not something to abstract away.

Attachments are the clearest place to see where that line runs, so here is the whole thing: putting a document on a booking is four steps on this platform. Create the transaction, update the balance on the finance account it belongs to, upload the file, then link the file to the transaction. It is more than four calls, because the balance update is a read and a write for the reasons above. Against our own endpoint it is one request: POST /accounting/transactions, the transaction as JSON and up to six files in the same multipart body. You never see the file endpoint, the voucher id it returns or the call that links them.

Three limits come with that, and they are yours to design around.

  • It ends at a suggestion. The call that ties a file to a transaction is named a hint, and that is exactly what it does. The document arrives in the Lexware web UI as suggested, and a person confirms it. Lexware exposes nothing stronger, so nobody can automate that last step, us included.
  • It needs an external reference from you. The link is made on a reference you send with the transaction, not on an id you get back. No reference, no link.
  • It is best effort, not atomic. A chain of calls has no shared transaction and no rollback. If the upload fails after the booking is written, the booking stays written, and your retry has to expect that.

One more thing to plan for, because it is in neither documentation. Step one needs the id of the finance account, and there is no endpoint that lists them. You can fetch one by id and that is all, so the id comes from creating the account or from a person reading it out of the Lexware UI. Watch the naming while you look for it: our accounts endpoint returns booking categories, which are a different thing entirely.

The four steps, the single request and the three limits were verified against our own implementation on 11 August 2026. Lexware documents the file endpoint but not this chain, so read this section as our first-hand account rather than as a quote from their reference.

Also worth knowing

Lexware does not allow load or performance testing against the public API, and they run no acceptance check either. Nobody signs off your integration, and nobody wants you hammering production to find out where it bends.

FAQ

Frequently asked questions

Is there a Lexware Office sandbox?

No. The public API exists only in Lexware's production environment. For development you create a Lexware test account, which is free and valid for 30 days and offers the functions of version XL with a few exclusions, Elster among them. You decide when you go live, because Lexware runs no acceptance check.

What are the Lexware Office rate limits?

A client may send two requests per second, and that limit covers all endpoints at the same time rather than one budget per endpoint. Exceeding it returns 429. The authorization server has separate limits that are not published, and a client that keeps pushing stays blocked until it slows down.

Why does my update return 409 Conflict?

Because Lexware uses optimistic locking. A PUT has to carry the version you received from your last GET of that record. If anything changed it in between, your version is stale and the write is refused with 409. The fix is to read the record again and retry with the new version.

Does Lexware Office support webhooks?

Yes, and they are the intended way to stay in sync, because Lexware asks you to avoid polling. Through Maesn you subscribe per customer to a resource and an event type. Two fields in the unified payload stay empty for Lexware, filterDate and userId, because Lexware does not send them.

Which plans can use the Lexware Office public API?

Lexware states that the public API is available from version XL upward, so your customer needs at least that plan to create an API key. A partnership with Lexware changes what is reachable, and Maesn supports both routes so your implementation stays the same either way.

Which currencies does Lexware Office support?

Only EUR. The documentation says so for both the price and the total price of a line item, and tax rates and codes follow German law. If your product handles other currencies, convert before you post and keep the original amount and rate on your side.

Build once on the Unified API.

Rate governors, version reads, per-customer subscriptions and key renewals are the same problem on every accounting system. Solve them once with Maesn instead of once per platform.