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/v1or your reverse-proxy hostname
Available recipes
| Recipe | Use case |
|---|---|
| Stripe → Solid Accounting invoices | Stripe webhook auto-creates invoices and posts payments |
| Zapier connector | No-code automation: form submission → contact, email subscribed → invoice, etc. |
| Power BI / Looker / Sigma reporting | Daily 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_idfield on creates so re-runs find the existing record - Check before create —
GET /invoices?external_id=Xfirst; create only if missing - Atomic upsert via
external_id—PUT /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
- REST API — endpoint reference
- Audit Log module — every API write is logged with the API key's identity
- Account & Billing → License keys — API access tier requirement