The API
Open in the demoRead your Entirova data from your own systems: reporting tools, dashboards, exports and scripts.
The API is read only for now, and webhooks tell your system when something happens. Creating records through the API is being built. Both are included on the Scale plan and above.
Make a key
- An Owner or Admin opens Settings Integrations & API, selects the API card, then New key.
- Name it after what will use it, and tick what it may read.
- Copy the key. It is shown once: Entirova keeps only a fingerprint of it, so nobody can show it to you again.
A key belongs to one organisation. The page lists each key with what it can read and when it was last used. Revoke stops a key working straight away. Making and revoking keys are recorded in the Audit trail.
Treat a key like a password. Anyone who has it can read what you allowed. Keep it on your server, never in a web page, an app people download, or a shared file. If it leaks, revoke it and make a new one.
Make a call
Every address starts with your Entirova site followed by /api/v1. Send the key in the Authorization header:
curl https://YOUR-CIRRO-SITE/api/v1/invoices?limit=50 \
-H "Authorization: Bearer cir_live_…"
| Address | What you get |
|---|---|
GET /api/v1 | What the API offers. No key needed. |
GET /api/v1/<resource> | A page of records. |
GET /api/v1/<resource>/<id> | One record. |
What you can read
| Resource | Permission on the key | Notes |
|---|---|---|
customers | Customers | Bank details and pictures are never included. Each has code (the customer ID) and, where given, firstName, lastName and company. |
suppliers | Suppliers | Bank details are never included. |
products | Products and services | With prices, costs and stock on hand. |
invoices | Invoices | With their lines and payments. |
bills | Bills | With their lines and payments. |
accounts | Chart of accounts | The id is the account code. |
journals | Journals | Each with its debit and credit lines. |
Records come back as Entirova stores them, with an id and an updatedAt time. A resource can only be read while its app is installed: for example, bills needs the Bills app.
Pages
A list comes back a page at a time, 50 records unless you ask for another size with limit (1 to 200):
{ "data": [ … ], "next": "inv_8f2k…" }
To get the following page, send next back as after: /api/v1/invoices?after=inv_8f2k…. When next is null you have everything.
Limits and errors
Each key may make 120 calls a minute. Every answer says how many are left in the x-ratelimit-remaining header.
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid | The key is missing, wrong or revoked. |
| 403 | scope | The key isn't allowed to read that resource. |
| 403 | app_not_installed | The app for that resource isn't installed. |
| 403 | plan | The organisation's plan doesn't include the API. |
| 404 | not_found | No such resource or record. |
| 405 | method | Only GET works in this release. |
| 429 | rate_limited | Too many calls. Wait the number of seconds in retry-after. |
An error always looks like { "error": { "code": "…", "message": "…" } }.
Webhooks
A webhook is an address on your own system that Entirova calls when something happens, so you don't have to keep asking.
- An Owner or Admin opens Settings Integrations & API, selects the Webhooks card, then New webhook.
- Enter a public
https://address and tick the events you want. - Copy the signing secret. It is shown once.
- Select Send test to get a
pingcall and check your side works.
| Event | When | What it carries |
|---|---|---|
invoice.created | An invoice is first saved, draft or not. | The invoice |
invoice.sent | An invoice leaves draft. | The invoice |
invoice.voided | An invoice is voided. | The invoice |
payment.received | A customer payment is recorded on an invoice. | The invoice and the payment |
bill.created | A bill is first saved. | The bill |
bill.approved | A bill is approved. | The bill |
payment.made | A payment to a supplier is recorded on a bill. | The bill and the payment |
customer.created | A customer is added. | The customer, without bank details |
supplier.created | A supplier is added. | The supplier, without bank details |
There is no "invoice paid in full" event. payment.received carries the invoice with all its payments, so your system can tell.
What a call looks like
Entirova sends a POST with JSON, usually within a minute of the event:
{ "id": "5c1e…", "event": "invoice.sent", "created": "2026-10-05T10:00:00Z", "attempt": 1,
"data": { "id": "inv_8f2k…", "number": "INV-1042", … } }
Answer with any 2xx status within 10 seconds. Do the slow work after you've answered.
Check it came from Entirova
Every call has a Cirro-Signature header: t=<time>,v1=<signature>. The signature is the HMAC-SHA256, in hex, of the time, a full stop and the body exactly as received, made with your signing secret. Work it out yourself and compare:
const crypto = require('crypto');
function fromCirro(rawBody, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!m || Math.abs(Date.now() / 1000 - m[1]) > 300) return false; // older than 5 minutes
const mine = crypto.createHmac('sha256', secret).update(m[1] + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(mine), Buffer.from(m[2]));
}
Always check the signature. Without it, anyone who learns your address could send you made-up events. Use the body exactly as it arrived, before any JSON parsing.
Retries
- If your address doesn't answer with a
2xx, Entirova tries again after 1, 5 and 30 minutes, then 2 and 12 hours, then gives up on that call. - A retry has the same
id(also in theCirro-Deliveryheader) and a higherattempt. Use the id to ignore a call you've already handled. - Calls can arrive out of order. Go by
created, not by arrival. - After 30 failures in a row the webhook is switched off, and the page says why. Send test switches it on again.
- Redirects aren't followed, and the address must be a public name: not an IP address or a local one.