API keys — creation, transport and limits
The key is the only credential for a third party — 40 hex characters shown once and stored as a hash. It has a name, a company, an optional expiry and a per-minute limit. Send it in a header; rotate by creating a new one and deleting the old.
Last updated: 2026-09-21
On this page
No session tokens
A third party has exactly one credential: the API key. No Sanctum tokens, no OAuth — and whatever needs an employee session (the two Dynamics print steps) is not callable from a server.
Creating a key
Settings → Integration → API keys → Add key:
| Field | Label | Rule |
|---|---|---|
name |
Client name | required — “SAP — Finance” |
company_id |
Company | required — the company this key writes into; a system serving two companies gets two keys |
expires_at |
Expiry (optional) | empty = never expires; a past date is rejected |
rate_limit_per_minute |
Requests / minute | 1–100000, default 60 |

After saving, the key is shown once: “Copy the key now — it won’t be shown again.” Only its SHA-256 hash is stored; there is no retrieval. The key is 40 hex characters (160 random bits).

Columns: ID · Name · Company · Status · Expiry · Requests / minute · Created · Actions. A legacy key without a company shows a No company — disabled badge and a banner with the count; the row’s company editor assigns one (with a confirmation that previously stamped cheques keep their company).

Transport
Three forms, in precedence order — use the first:
| # | Form | Note |
|---|---|---|
| 1 | Header X-API-KEY: <key> |
use this |
| 2 | ?api_key=<key> in the URL |
leaks into server logs and Referer — avoid |
| 3 | secret field in the body |
redacted from the audit log, but still transmitted |
Rejection ladder
The first matching condition ends the request:
| # | Condition | Status | Body |
|---|---|---|---|
| 1 | Integrations disabled | 503 | {"error":"Integrations are currently disabled"} |
| 2 | No key | 401 | {"error":"Unauthorized"} |
| 3 | Key unknown or inactive | 401 | same text — deliberately indistinguishable |
| 4 | Key expired | 403 | {"error":"API key expired"} |
| 5 | Over the limit | 429 | {"error":"Too many requests","retry_after_seconds":N} |
Rate limiting
A fixed 60-second window per key (not per IP). With a limit of 60, requests 1–60 pass and 61 is the first rejected; retry_after_seconds says when to retry. The limit applies after authentication — a wrong key is not rate-limited (see Security).
Rotation and deletion
There is no “revoke” button: deleting a key is final (“systems using it will no longer be able to connect”). Rotation = create a new key → swap it in the external system → delete the old one. Automatic expiry via expires_at. Deleting a key keeps its audit rows, unattributed.
Key scope
A key opens every endpoint (no partial scopes) inside its company: every cheque created with it is stamped with the key’s company; company_id in the payload is optional — if sent and different ⟹ 422 naming both companies. A reference from another company (bank_account_id · cheque_book_id · cheque_leaf_id · partner_id) ⟹ 422. A key without a company ⟹ an explicit 422 until an administrator assigns one. See Security.
Related
Did this page answer your question?
Thanks — your feedback helps us improve the guide.
Couldn't send — please try again shortly.