DEVELOPERS
Integrations API
Drive your CountBooks ledger from an external system. The API does exactly four things: create a GL account, look up an existing GL account to confirm its name, post a forex journal entry, and reverse one — all through one endpoint. Generate a key from the console's Integrations page (Organization → Integrations).
Authentication
Every request is a POST to the same endpoint, with your API key in an X-API-Key header. A key is scoped to exactly one company — you never send a companyCode yourself, it's resolved from the key.
POST /api/v1/execute
Content-Type: application/json
X-API-Key: cbk_your_company_api_key
{
"operation": "request.getGlAccount",
"payload": { "accountCode": "500-501-0001" }
}Every response is the same envelope shape, whether it succeeded or failed:
{
"responseCode": "000",
"responseMessage": "GL account found",
"result": { ... }
}responseCode is "000" on success. Anything else is an error — read responseMessage for what went wrong.
Using the company API
- Select the company in the console and open Organization → Integrations. The company needs an active subscription with integrations enabled, and your user needs permission to manage integrations.
- Generate a key and copy it when shown. Configure your server with the API origin and company key, and send requests over HTTPS using
X-API-Key. - Set
operationto one of the fourrequest.*names below. Put the operation's fields insidepayload. - Check
responseCodefor every response. Each of these operations returns a singleresultobject.
The key's company scope, revoked status, and subscription's integration entitlement are checked on every call. A console bearer token cannot call these external operations.
Concepts
- GL account — a line in your chart of accounts (an asset, liability, income, or expense account), identified by an
accountCodelike500-501-0001. - Forex journal entry — a balanced double-entry posting: any number of lines, as long as total debits equal total credits. A currency movement typically has two legs (a debit and a matching credit).
- Reversal (credit note) — an offsetting entry that cancels a journal you posted earlier, with every line's debit/credit flipped. The original entry stays on the books; the reversal is a new, linked entry.
Journal entries posted through this API are recorded immediately (no approval step) — the calling system is treated as the source of truth for what it sends.
request.createGlAccount
Creates a new GL account. Always mints a new, auto-numbered account.
Request
{
"operation": "request.createGlAccount",
"payload": {
"accountName": "USD Bank Account",
"classCode": "100",
"subclassCode": "102",
"currencyCode": "USD",
"description": "Optional"
}
}Response result
{
"accountCode": "100-102-0001",
"accountName": "USD Bank Account",
"classCode": "100",
"subclassCode": "102",
"normalBalance": "DR",
"currencyCode": "USD",
"status": 1
}request.getGlAccount
Looks up an existing GL account by its code and returns it, so you can confirm the account exists and read back its name. Read-only — it never creates. Errors if the account is not found or inactive. Pass an optional currencyCode to also assert the account is denominated in that currency.
Request
{
"operation": "request.getGlAccount",
"payload": {
"accountCode": "100-102-0001"
}
}Also validate the currency
{
"operation": "request.getGlAccount",
"payload": {
"accountCode": "100-102-0001",
"currencyCode": "USD"
}
}Response result
{
"accountCode": "100-102-0001",
"accountName": "USD Bank Account",
"classCode": "100",
"subclassCode": "102",
"normalBalance": "DR",
"currencyCode": "USD",
"status": 1
}request.postJournalEntry
Posts a balanced forex journal entry — any number of lines. Posted immediately. exchangeRate converts amounts into your company's base currency (e.g. KES per 1 USD); it applies to every line that doesn't set its own, and lines with no rate post at 1. narration describes the entry and is copied to each line that has no narration of its own. Debits must equal credits in base currency (amount × exchange rate); the totals returned are in base currency. externalReference is optional and makes retries idempotent.
Fields
currencyCode | Required. Currency of the entry, e.g. "USD". |
exchangeRate | Optional. Rate to the base currency for the whole entry. Must be positive. Defaults to 1. |
narration | Optional. Description of the entry, up to 255 characters. Used for each line without its own narration. |
reference | Optional. Your document reference. |
journalDate | Optional. YYYY-MM-DD; defaults to today. |
externalReference | Optional. Your unique transaction ID — makes retries idempotent. |
lines[].glAccountCode | Required. The GL account to post to. |
lines[].dc | Required. "DR" or "CR". |
lines[].amount | Required. Positive amount in the line's currency. |
lines[].currencyCode | Optional. The line's own currency, when it differs from the entry. |
lines[].exchangeRate | Optional. Overrides the entry exchangeRate for this line (use it for the base-currency leg, e.g. 1). |
lines[].narration | Optional. Overrides the entry narration for this line, up to 255 characters. |
Request
{
"operation": "request.postJournalEntry",
"payload": {
"currencyCode": "USD",
"exchangeRate": 129.5,
"narration": "USD/KES sale to walk-in customer",
"reference": "FX-2041",
"lines": [
{
"glAccountCode": "100-102-0001",
"dc": "DR",
"amount": 5000,
"narration": "USD received"
},
{
"glAccountCode": "400-100-0001",
"dc": "CR",
"amount": 5000
}
],
"externalReference": "fx-txn-88213"
}
}Two currencies in one entry (balanced in base currency)
{
"operation": "request.postJournalEntry",
"payload": {
"currencyCode": "USD",
"exchangeRate": 129.5,
"narration": "Sold USD 1,000 for KES",
"lines": [
{
"glAccountCode": "100-101-0001",
"dc": "DR",
"amount": 129500,
"currencyCode": "KES",
"exchangeRate": 1
},
{
"glAccountCode": "100-102-0001",
"dc": "CR",
"amount": 1000
}
]
}
}Response result
{
"journalNumber": "JRN-2026-000482",
"totalDebit": 647500,
"totalCredit": 647500,
"narration": "USD/KES sale to walk-in customer"
}request.reverseJournalEntry
Reverses a wrong forex entry (a credit note): books an offsetting journal with every line flipped, for an entry this API posted earlier. Identify the entry by the journalNumber returned when you posted it. narration is optional (defaults to "Reversal of <journalNumber>").
Request
{
"operation": "request.reverseJournalEntry",
"payload": {
"journalNumber": "JRN-2026-000482"
}
}Identify by your own externalReference instead
{
"operation": "request.reverseJournalEntry",
"payload": {
"externalReference": "fx-txn-88213",
"narration": "Reversed: wrong rate"
}
}Response result
{ "journalNumber": "JRN-2026-000488", "reversalOf": "JRN-2026-000482" }Idempotency
request.postJournalEntry accepts an optional externalReference — your own identifier for the transaction. If you retry a call with the same value (e.g. after a network timeout), the already-posted journal is returned instead of creating a duplicate.
request.reverseJournalEntry is idempotent too: a journal that has already been reversed returns its existing reversal rather than booking a second one, so a retried reversal is always safe.