Integration recipes

These are working examples — concrete, opinionated, end-to-end — for connecting Solid Accounting to other systems via the REST API. Different from the API reference; that's the spec, these are the assembly instructions.

Every recipe assumes you have:

  • Solid Accounting Professional or Accountant tier (the API is a paid-tier feature)
  • API enabled (Settings → API & Integrations → Enable Integration API)
  • API key copied to a password manager
  • The localhost endpoint reachable — http://localhost:21384/api/v1 or your reverse-proxy hostname

Available recipes

RecipeUse case
Stripe → Solid Accounting invoicesStripe webhook auto-creates invoices and posts payments
Zapier connectorNo-code automation: form submission → contact, email subscribed → invoice, etc.
Power BI / Looker / Sigma reportingDaily ETL pulling balances and transactions into your BI stack
Custom webhook receiver (coming soon)Generic pattern for any third-party that POSTs to your URL
Inventory sync from a warehouse (coming soon)Push inventory adjustments and PO receipts from your warehouse system
Payroll provider sync (coming soon)Daily / per-pay-period pull from any payroll system

Pattern overview

Every integration tends to follow one of these shapes:

Inbound webhook → Solid

Third-party event (e.g. Stripe charge succeeded)
       ↓
Your service receives the webhook
       ↓
Your service POSTs to Solid's API (e.g. POST /invoices)
       ↓
Solid validates double-entry, posts the journal entry

Most "automate-the-business" integrations are this shape. The work is mapping each third-party event to the right Solid API call.

Periodic pull from Solid → external

Cron job runs (say daily)
       ↓
Job hits Solid's API (GET /balances, GET /reports/balances, etc.)
       ↓
Job transforms and pushes to the external system (BI tool, spreadsheet, etc.)

Most "reporting and analytics" integrations are this shape. Usually one cron job with several API calls + a transformation step.

Bidirectional sync

Two systems each have part of the truth
       ↓
Reconciliation logic in the middle decides who wins on conflict
       ↓
Both sides updated to converge

Hardest to get right; most likely to need custom code. The recipes below avoid this where possible.

Things to know before you build

Use a long-running service, not a script-on-demand

The API is local-only by default (binds to 127.0.0.1:21384). For integrations that fire from outside your machine — a Stripe webhook, a Zapier action, a remote BI tool — you need either:

  • A long-running service on the same machine that proxies the requests
  • A reverse proxy (nginx, Caddy) exposing the API to a controlled outside hostname
  • A VPN with the API binding on 0.0.0.0

We recommend the reverse proxy with TLS terminated at the proxy — see API → Network access.

Idempotency

Solid's API isn't yet idempotency-key-aware. Re-running the same POST /invoices will create a duplicate invoice. Patterns:

  • Use external IDs — set the external_id field on creates so re-runs find the existing record
  • Check before createGET /invoices?external_id=X first; create only if missing
  • Atomic upsert via external_idPUT /invoices/external/X (planned for v26.3)

The Stripe recipe walks through external-ID handling concretely.

Rate limits

Default is 600 requests per minute per API key. For bulk imports (creating thousands of records), use the .qbextract migration tool path instead — it bypasses the API entirely.

Errors

Every endpoint returns a success: true|false envelope. Build error handling around success === false, log the error.code, and surface error.message to operators. Never swallow errors silently — a failed integration that "looks like" it ran is the worst kind of bug.

Common stumbling blocks

"My integration works locally but fails in production." Almost always a network-access issue. The API binds to 127.0.0.1 by default; a service on a different machine can't reach it. Check the API → Network access section.

"Authentication keeps failing." Most common: the X-API-Key header is mis-cased or missing. The header is case-insensitive but its presence is required.

"My posted invoice has the wrong customer." Customer references go by UUID (customer_id), not by name. Look up the UUID with GET /contacts?display_name=... and use the result's id.

"Duplicate journal entries every time my webhook fires." Idempotency — see above. Use external IDs.

"It worked yesterday, now I'm getting 401." API keys can be revoked from Settings → API & Integrations (UI) or expire if explicitly time-limited. Generate a new one; rotate it through your secret store.

Cross-references

Updated May 2, 2026
Edit this page on GitHub →
Was this helpful?

We use this to prioritize which docs to improve. No tracking, no email follow-up.