Skip to content

For founders and SaaS companies

How to connect your app to accounting software: CSV, integrations, API, MCP

Last reviewed SUPPORTED WITH LIMITATIONS

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.

OptionWho runs itLatencyError handlingFits when
CSV export / importA personDays to weeksManual; duplicates are easy to createLow volume, periodic hand-off, no engineering time
Native integrationThe vendorMinutes to hoursWhatever the vendor built; often opaqueYour other tool is on the vendor's list and the mapping suits you
Public APIYour codeSecondsYours: retries, idempotency, loggingData originates in your app; you need control and an audit trail
MCP serverAn AI assistant under a person's supervisionInteractiveRead-mostly; writes limited to draftsQuestions 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:

  1. The first request under a key does the work and stores the result.
  2. A repeat with the same key and the same body returns the stored result — the same record, not a new one.
  3. A repeat with the same key and a different body is refused, because a key stands for one intention.
  4. 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 prefix greif_live_ or greif_test_, scopes such as customers:write, invoices:write, transactions:write and reports:read. Endpoints for customers (GET/POST /customers), invoices (GET /invoices, POST /invoices/drafts), transactions (GET/POST /transactions) and GET /reports/outstanding. One company per key; GET /me names it. KRONENWERK stores only a hash of a key; keys are revoked or rotated on the developer screen.
  • Idempotency. Idempotency-Key is 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 carries KRONENWERK-Signature (t=<unix seconds>,v1=<hex HMAC-SHA256 over t.raw_body>, 5-minute tolerance), KRONENWERK-Event-Id, KRONENWERK-Event, KRONENWERK-Delivery, KRONENWERK-Attempt and an Idempotency-Key for 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 as list_receivables and get_profit_and_loss, and draft tools create_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); 429 with Retry-After when 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.

Sources

  1. KRONENWERK developer documentation read on
  2. IETF — The Idempotency-Key HTTP Header Field (Internet-Draft, httpapi WG) read on
  3. Model Context Protocol — Specification 2025-06-18 read on
  4. Stripe documentation — Receive Stripe events in your webhook endpoint read on

How KRONENWERK handles this

E-invoicing in the product Countries

Read next