Skip to main content

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:

ClaimRequirement
issThe authorization server's identifier, as a URL: https://account[.{env}].kobana.com.br
audThis API's URL: https://api.finance[.{env}].kobana.com.br. May be a list — the rule is contains, not equals
expRequired. A token without an expiry is refused
orgThe Kobana account the token acts for. It is what resolves the organization here, and it is required
subThe person (users.public_id). Carried for audit and attribution; it scopes nothing
jtiUnique 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.

ResourceScopeWhat the read covers
Financial accountsfinance/accountsaccounts and balances
Bank catalogfinance/banksread-only, there is no write
Payablesfinance/payablespayable bills
Receivablesfinance/receivablesreceivable bills
Sending to the bankfinance/payable_dispatchesthe payment dispatches and their state
Issuing a chargefinance/receivable_chargesthe charges (bank slip/Pix) issued
Payment receiptsfinance/receiptsread-only
Received fiscal documentsfinance/fiscal_documentsread-only
Transactionsfinance/transactionsentries and transfers
Reconciliationsfinance/reconciliationsreconciliations (the write covers create and dissolve)
Projected cash flowfinance/cash_flowread-only
Companiesfinance/companies
Peoplefinance/people
Categoriesfinance/categories
Classification centersfinance/cost_centersthe scope names the grant, hence cost_centers and not classification-centers as in the URL
Automatic rulesfinance/automatic_rulesthe write includes reordering
Taxesfinance/taxes
Attachmentsfinance/attachmentsthe write covers upload and management
Resource catch-allfinance/allreads every resource above — and grants no verb at all
Global verbwriteevery 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/all grants all reads, and no writes. Writing needs the verb: finance/all write.
  • finance/receipts lists receipts but does not download the document: download_url points at the attachments resource, so fetching the bytes also requires finance/attachments.
  • finance/fiscal_documents lists received notes but does not download the XML: xml_download_url points at the attachments resource, so fetching the bytes also requires finance/attachments.
warning

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:

LevelWhat it reaches
readevery read (GET)
writeevery read and every write, except moving money
alleverything, 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) — read when the integration only reads, and all only 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.