Authentication
The Smart Finance API uses a Kobana Account access token to authenticate every request.
The token
The token is an RS256 JWT issued by the Kobana authorization server (account.kobana.com.br). This API only verifies it, against the public key that server publishes — it holds no signing key and cannot mint a token at all.
Checked on every request:
| Claim | Requirement |
|---|---|
iss | The authorization server's identifier, as a URL: https://account[.{env}].kobana.com.br |
aud | This API's URL: https://api.finance[.{env}].kobana.com.br. May be a list — the rule is contains, not equals |
exp | Required. A token without an expiry is refused |
org | The Kobana account the token acts for. It is what resolves the organization here, and it is required |
sub | The person (users.public_id). Carried for audit and attribution; it scopes nothing |
jti | Unique token id — the basis for revocation and audit |
The workspace comes from the X-Workspace-Id header: org names the organization, and an organization may have several workspaces. It is a selector, not a grant — a workspace belonging to another organization is not reachable by guessing its id. Without the header, when there is more than one workspace, the answer is 400 WORKSPACE_REQUIRED.
:::caution Change from the dashboard-generated token The credential used to be a token generated in the platform, with a secret shown only once. The same value signed and verified it, so a leak on either side was a leak of issuance itself. Now the authorization server issues, and this API only verifies. :::
If the authorization server is unreachable, the answer is 5xx, never 401 — a 401 there would send you debugging a perfectly good credential during an outage of ours.
How to send the token
Include the Authorization: Bearer <token> header on every request.
export KOBANA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx
curl -i \
-H "Authorization: Bearer $KOBANA_TOKEN" \
-H 'Content-Type: application/json' \
-H 'User-Agent: My System (contact@mycompany.com)' \
-X GET 'https://api.finance.kobana.com.br/v1/financial-accounts'
Scopes
Scopes have two orthogonal axes: the resource, always prefixed by the app (finance/payables), and the verb, a global scope with no prefix (write). A resource without a verb means read. Writing requires both: the resource scope and the verb.
| Resource | Scope | What the read covers |
|---|---|---|
| Financial accounts | finance/accounts | accounts and balances |
| Bank catalog | finance/banks | read-only, there is no write |
| Payables | finance/payables | payable bills |
| Receivables | finance/receivables | receivable bills |
| Sending to the bank | finance/payable_dispatches | the payment dispatches and their state |
| Issuing a charge | finance/receivable_charges | the charges (bank slip/Pix) issued |
| Payment receipts | finance/receipts | read-only |
| Received fiscal documents | finance/fiscal_documents | read-only |
| Transactions | finance/transactions | entries and transfers |
| Reconciliations | finance/reconciliations | reconciliations (the write covers create and dissolve) |
| Projected cash flow | finance/cash_flow | read-only |
| Companies | finance/companies | |
| People | finance/people | |
| Categories | finance/categories | |
| Classification centers | finance/cost_centers | the scope names the grant, hence cost_centers and not classification-centers as in the URL |
| Automatic rules | finance/automatic_rules | the write includes reordering |
| Taxes | finance/taxes | |
| Attachments | finance/attachments | the write covers upload and management |
| Resource catch-all | finance/all | reads every resource above — and grants no verb at all |
| Global verb | write | every write (POST, PUT, PATCH, DELETE) on the resources the token carries |
Two examples: finance/payables reads payables and cannot write; finance/payables write also creates, updates and deletes them, and still cannot reach receivables.
The granular form finance/payables:write is an exception of the grammar, not the default shape: it satisfies both axes for that one resource, and exists for "read one resource, write another". finance/* is a registration-time wildcard and is never issued in a token — comparison here is always literal. In the per-resource grammar, reading is the resource without a verb; the read that does exist is the Kobana Account's access level, below.
Resolution rules:
finance/allgrants all reads, and no writes. Writing needs the verb:finance/all write.finance/receiptslists receipts but does not download the document:download_urlpoints at the attachments resource, so fetching the bytes also requiresfinance/attachments.finance/fiscal_documentslists received notes but does not download the XML:xml_download_urlpoints at the attachments resource, so fetching the bytes also requiresfinance/attachments.
Moving money is its own resource, not its own verb. Sending a payment to the bank and issuing a charge are sensitive to a different degree than the rest of writing, so each is a resource: finance/payable_dispatches and finance/receivable_charges. A token with finance/payables write records a bill and cannot dispatch a payment; finance/all reaches neither one even with the write verb, because the catch-all covers reads and money demands the resource by name. So an ordinary operational token neither moves money nor bills anybody.
Access level — what is enforced today
The scope is enforced on every route. The per-resource scopes above are not registered on the Kobana Account yet, so the tokens it issues do not carry them — but every token carries the access level you granted when authorizing, and that is what the API checks:
| Level | What it reaches |
|---|---|
read | every read (GET) |
write | every read and every write, except moving money |
all | everything, including sending a payment to the bank and issuing a charge |
delete, import and export on their own do not write. A client that registers itself on the Kobana Account — an AI assistant connected through MCP, for example — gets only read.
Any of the vocabularies is enough: the access level, the per-resource scopes above or the finance.<resource>[.write] scopes of development tokens.
When the token carries per-resource scopes — the personal token you create on the Kobana Account, for example —, they define the reach, and read, write and all only apply within those resources: finance/payables read write reads and writes payables and nothing else; finance/all read reads everything and writes nothing. Moving money requires its own resource (finance/payable_dispatches, finance/receivable_charges) with write or all. When it is missing, the answer is 403 INSUFFICIENT_SCOPE, with required, granted and accepted_levels — the levels that would have been enough — in details.
Best practices
- Never commit tokens to git. Use environment variables or secret vaults (AWS Secrets Manager, Vault, etc.).
- Use distinct tokens per integration — this makes it easy to rotate/revoke without taking everything down.
- Ask for the minimum scopes required (principle of least privilege) —
readwhen the integration only reads, andallonly for whoever needs to move money. - Revoke any leaked token immediately.
- Always point at
https://— calls on port 80 are redirected to 443, but clients do not resend the body on a redirect.