How to authenticate, what you can read and write, and how to make retries safe — for the systems that plug into the accounting engine.
Pick a personal access token for a quick, single-tenant integration, or OAuth2 when you need a standard authorization flow or a multi-client integration.
The fastest way in. A user with the api_token.manage permission (owner or admin) creates a token directly in Console, choosing which abilities it carries — for example events:read and events:write. The token is shown once at creation and never stored in plain text, so save it immediately.
List accounts with a personal access token
curl https://console.fincoledger.com/api/v1/accounts \
-H "Authorization: Bearer <your-personal-access-token>"Portal is the OAuth2 authorization server for the platform. It supports two grant types: Authorization Code with PKCE for integrations acting on behalf of a logged-in user, and Client Credentials for server-to-server integrations acting as a service account. Either way, the resulting access token is used against Console exactly like a personal access token.
Use this when a human is present to approve access — for example, a third-party app requesting scoped access to one tenant. Redirect the user to GET /oauth/authorize with response_type=code, your client_id, redirect_uri, requested scope, and a PKCE code_challenge using code_challenge_method=S256 (plain challenges are rejected). After the user approves, Portal redirects back with an authorization code, which you exchange for a token:
Exchange an authorization code for an access token
curl -X POST https://portal.fincoledger.com/api/oauth/token \
-d grant_type=authorization_code \
-d client_id="<your-client-id>" \
-d client_secret="<your-client-secret>" \
-d redirect_uri="<your-redirect-uri>" \
-d code="<code-from-the-redirect>" \
-d code_verifier="<your-pkce-code-verifier>"Use this for unattended, service-account integrations — no user is present at request time. Exchange your client ID and secret directly for a token, scoped to whichever tenant the client was registered under:
Get a token with the client credentials grant
curl -X POST https://portal.fincoledger.com/api/oauth/token \
-d grant_type=client_credentials \
-d client_id="<your-client-id>" \
-d client_secret="<your-client-secret>"A token can never see or touch data outside the tenant it was issued for, and it can never do more than the person who issued it is allowed to do.
A personal access token is bound to the tenant it was created in; an OAuth2 token is bound to the tenant the client was registered against. There is no way to pass a different tenant at request time — every call runs against the token's own tenant only.
Every endpoint checks two things at once: the ability the token was granted (e.g. events:write) and the RBAC permission of the user who issued it (e.g. event.create). A token can never exceed its issuing user's access — if that user's permissions are later reduced or revoked, the token's effective access shrinks with them.
Networks fail and retries happen — the Idempotency-Key header makes it safe to retry a write without creating a duplicate.
Send a client-generated UUID as the Idempotency-Key header on any POST, PATCH, or DELETE request. Reuse the exact same key for every retry of that same logical request. If the retry matches the original request, you get back the original response instead of a second write; if the same key arrives with a different request body, the server rejects it with 409 Conflict rather than guessing which one you meant.
Post a business event with a retry-safe key
curl -X POST https://console.fincoledger.com/api/v1/events \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-d '{
"event_type": "receive_money",
"event_date": "2026-07-01",
"amount": 1500.00,
"currency_code": "TWD",
"account_id": 101,
"contra_account_id": 205,
"description": "Customer deposit"
}'
# Retrying with the SAME Idempotency-Key returns the same 201 response
# instead of creating a second event — safe to run again after a timeout.The write surface is deliberately narrow: everything you create goes in through business events, and the accounting engine handles the double-entry bookkeeping from there.
Create, edit, or delete a draft business event, then post or void it — POST /api/v1/events and its /events/{id} actions are the only write surface. Submit what happened in plain terms (receive money, spend money, transfer money, or an accrual) and the engine produces the balanced journal entry, the same way the New Transaction screen does inside the app.
Chart of accounts, journals, trial balance, income statement, balance sheet, inventory valuation, AR customers/invoices/receipts, AP vendors/bills/payments, inventory items/warehouses/movements, and bank transactions are all available as read endpoints under /api/v1 — enough for an integrator to reconcile their own records against the ledger without ever writing to it directly.
Explore the API surface yourself in the demo environment, or get in touch for integration credentials.