There are four ways to get data from your application into accounting software: export a CSV and import it, use a native integration the vendor built, call a public API from your own code, or let an AI assistant work through an MCP server. For anything that runs every day the API is the honest choice, and a good API connection comes down to five decisions: what to sync, in which direction, how to make retries safe (idempotency), how to learn about changes (webhooks), and where the key lives.
The four options, and when each is enough
CSV is enough for a once-a-quarter hand-off to an accountant. A native integration is enough when the two products you use happen to be the two products the vendor connected. An API is what you need when the data originates in your own system and has to arrive complete, every day, without a person in the loop. MCP is a fifth layer on top of the API for people who ask an assistant questions rather than write code.
| Option | Who runs it | Latency | Error handling | Fits when |
|---|---|---|---|---|
| CSV export / import | A person | Days to weeks | Manual; duplicates are easy to create | Low volume, periodic hand-off, no engineering time |
| Native integration | The vendor | Minutes to hours | Whatever the vendor built; often opaque | Your other tool is on the vendor's list and the mapping suits you |
| Public API | Your code | Seconds | Yours: retries, idempotency, logging | Data originates in your app; you need control and an audit trail |
| MCP server | An AI assistant under a person's supervision | Interactive | Read-mostly; writes limited to drafts | Questions and preparation, not unattended automation |
The options are not exclusive. A common shape is API for the daily flow, MCP for the founder who asks "who has not paid?", and a CSV export at year end for the accountant's own software.
What to sync: customers, invoices, payments — in that order
Sync the objects the ledger needs to produce a correct invoice and match a payment, and nothing more. In practice that is three objects with a dependency between them: a customer must exist before an invoice can reference it, and an invoice must exist before a payment can settle it.
- Customers
- Name, address, country, VAT ID, whether business or consumer, and your own external identifier. The country and VAT status are what decide the tax treatment; get them right at creation, not on the invoice.
- Invoices
- Lines with description, quantity, unit price and the kind of supply; the currency; due date; a reference to your order or subscription. Do not send a tax rate — send the facts and let the ledger decide, or you will re-implement VAT law in your app.
- Payments
- Amount, date, currency, and the invoice or invoices it settles. If a payment processor is involved, the processor's transaction ID is the key that later reconciles the payout.
Direction matters. Customers and invoices usually flow from your app into the ledger. Payment status often flows back — the ledger sees the bank feed, your app wants to know the invoice is paid. Reports (outstanding receivables, profit and loss) flow back only. Decide per object which system is the source of truth and never write the same field from both sides.
What not to sync: your product's internal events (logins, feature usage), draft orders that may never be invoiced, and anything the ledger cannot act on. Every object you push is one you will have to keep consistent.
Idempotency: the response that never arrives
A connection drops after the ledger created the customer but before your app got the reply. Without protection you either lose the record or create it twice, and a duplicate customer surfaces weeks later when an invoice reaches the wrong copy. The fix is an idempotency key: a value you choose per intended write, sent in a header, that lets the server recognise a repeat.
The IETF's Internet-Draft for the header states its purpose plainly: "The HTTP Idempotency-Key request header field can be used to make non-idempotent HTTP methods such as POST or PATCH fault-tolerant." The draft has expired without becoming an RFC, but the pattern is the industry convention and the header name is what most APIs use. The rules that make it work on the server side:
- The first request under a key does the work and stores the result.
- A repeat with the same key and the same body returns the stored result — the same record, not a new one.
- A repeat with the same key and a different body is refused, because a key stands for one intention.
- Keys expire after a window, after which the value counts as new work.
On the client side: generate the key when the intention is formed (a UUID stored with your own order row), not when the request is sent, so that a retry after a crash reuses it. Retry with backoff on network errors and on 5xx; do not retry on 4xx other than 429.
Webhooks: learn about changes without polling
A webhook is an HTTP request the accounting software sends to your URL when something happens — an invoice was issued, a payment was recorded. It replaces polling, but it comes with three obligations: verify the signature, expect duplicates, and respond fast.
Stripe's guidance is the reference most developers know and it applies to any provider: "Always verify that webhook events originate from Stripe before acting on them"; "webhook endpoints might occasionally receive the same event more than once", which you guard against "by logging the event IDs you've processed"; and your endpoint "must quickly return a successful status code (2xx) before any complex logic that could cause a timeout". Stripe also notes it "doesn't guarantee the delivery of events in the order that they're generated" — so treat each event as a pointer and fetch the current state if order matters.
Signature verification generally follows one pattern: a header with a timestamp and an HMAC over timestamp.raw_body; reject if the timestamp is outside a tolerance window (replay protection), recompute the HMAC with the endpoint secret over the raw bytes, and compare in constant time. Frameworks that parse and re-serialise JSON before you can read the body break this; read the raw body.
Keeping the key safe
An accounting API key can read every customer and invoice of the company and create drafts. Treat it like a database password: store it in a secrets manager or environment variable, never in source control or a mobile app, and give it only the scopes the integration uses.
- Least scope. A dashboard that only reads receivables needs a read scope, not a write scope. Reading and writing are separate grants.
- One key per integration. So that one can be revoked without breaking the others, and so that the audit trail says which system did what.
- Test keys in test, live keys in production. The key's prefix should make the environment obvious in a log line.
- Rotation. A provider that stores only a hash of the key cannot show it to you again — which is the correct design. Plan for rotation from the start: issue the new key, switch, revoke the old.
- Webhook secrets are keys too. Same storage, same rotation.
The MCP specification adds a point about assistants: "Hosts must obtain explicit user consent before invoking any tool", and tools "represent arbitrary code execution and must be treated with appropriate caution". An MCP server in front of an accounting ledger should therefore expose reads and preparation, not irreversible actions, and the person at the keyboard stays responsible.
How KRONENWERK handles this
KRONENWERK offers all four paths; the API, webhooks, MCP server and integration directory are part of the Enterprise plan (see plans). SUPPORTED WITH LIMITATIONS
- API.
https://kronenwerk.org/api/extern/v1, Bearer API key with prefixgreif_live_orgreif_test_, scopes such ascustomers:write,invoices:write,transactions:writeandreports:read. Endpoints for customers (GET/POST /customers), invoices (GET /invoices,POST /invoices/drafts), transactions (GET/POST /transactions) andGET /reports/outstanding. One company per key;GET /menames it. KRONENWERK stores only a hash of a key; keys are revoked or rotated on the developer screen. - Idempotency.
Idempotency-Keyis required on every POST. Same key and body: the same record comes back; same key and a different body: refused; keys live 24 hours. Details on idempotency. - Webhooks. Events
invoice.issued,invoice.paid,invoice.cancelled,purchase.recorded,payment.recorded. Each delivery carriesKRONENWERK-Signature(t=<unix seconds>,v1=<hex HMAC-SHA256 over t.raw_body>, 5-minute tolerance),KRONENWERK-Event-Id,KRONENWERK-Event,KRONENWERK-Delivery,KRONENWERK-Attemptand anIdempotency-Keyfor deduplication. HTTPS only, retries until accepted, redirects not followed. See webhooks. - MCP. A server at
/api/extern/mcp(Streamable HTTP, same key) with read tools such aslist_receivablesandget_profit_and_loss, and draft toolscreate_invoice_draft,create_transaction,add_transaction_note. No tool issues an invoice, sends mail, moves money or changes settings. - Rate limit. A budget of 240 requests per key, refilling continuously (about two per second);
429withRetry-Afterwhen exceeded.
POST /api/extern/v1/customers HTTP/1.1
Host: kronenwerk.org
Authorization: Bearer greif_test_…
Idempotency-Key: 6f1c2a8e-4b3d-4a21-9d77-0c5e1f9b2a44
Content-Type: application/json
{"name": "Example SRL", "country": "BE", "vatId": "BE0123456789", "email": "ap@example.be"}
A worked example of the three flows is on connecting a SaaS to accounting; the full surface is described on the accounting API page and in the reference.
Frequently asked questions
Should my app calculate VAT and send the rate to the accounting software?
No. Send the facts — customer country, business or consumer, kind of supply — and let the ledger decide and record the verdict. Duplicating VAT logic in your app is how the two systems drift apart.
Can I issue invoices directly from my code with KRONENWERK?
You create drafts over the API; issuing happens in the product after validation. That keeps the legal step — number assignment, e-invoice generation, VIES check — under a person's control.
Do I need webhooks if I can poll?
Polling works at low volume but costs rate-limit budget and adds delay. Webhooks tell you when an invoice is paid within seconds; poll only as a fallback to reconcile missed events.
Where should the API key live in a mobile or browser app?
Nowhere. A key in a client can be extracted. Keep it on your server and let the client talk to your server.
What is the difference between the API and the MCP server?
Same key, same company, same data. The API is for your code; the MCP server is for an AI assistant under a person's supervision, limited to reads and drafts.