Skip to main content

Authentication

The Magic Dashboard 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 issue any token. The org claim identifies your organization: all returned data is automatically scoped to it.

Requirements checked on every request:

ClaimRequirement
issThe Authorization Server identifier, as a URL: https://account[.{env}].kobana.com.br
audThis API's URL: https://api.pages[.{env}].kobana.com.br. May be a list — the rule is contains, not equality
expRequired. A token without expiration is refused
orgThe Kobana account the token acts on behalf of — it resolves the organization here, and it is required
subThe person (users.public_id). Used for auditing and attribution; it scopes nothing
jtiUnique token id — the basis for revocation and auditing

:::caution Change from the API key Until September 2026 the credential was a key created in the dashboard (kb_…), with a public prefix and a secret shown only once. The key was both the credential and what verified it — whoever read the key store could act as any organization, and every rotation was a conversation with a customer. Now the Authorization Server issues tokens and the platform only verifies them. A key created in Settings → API no longer authenticates this API. :::

Check the environment

The token is different between Sandbox and Production — one environment's aud is not valid in the other. Do not reuse tokens across environments.

Sending the token​

Include the Authorization: Bearer <token> header in every request.

export KOBANA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx

curl -i \
-H "Authorization: Bearer $KOBANA_TOKEN" \
-H 'Content-Type: application/json' \
-H 'User-Agent: My Name (myname@example.com)' \
-X GET 'https://api.dashboards.sandbox.kobana.com.br/v1/organization'

Valid request​

With a valid token, the response carries the authenticated Organization's data.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Request-Id: req_01HABCDEF

{
"id": "org_01HORG",
"name": "My Company",
"email": "contact@mycompany.com",
"document_type": "cnpj",
"document_number": "12345678000190",
"environment": "sandbox",
"created_at": "2026-01-01T00:00:00.000Z"
}

Invalid request​

A missing, malformed or revoked token returns 401 Unauthorized.

curl -i \
-H "Authorization: Bearer invalidtoken" \
-H 'Content-Type: application/json' \
-H 'User-Agent: My Name (myname@example.com)' \
-X GET 'https://api.dashboards.sandbox.kobana.com.br/v1/organization'
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{
"error": {
"code": "invalid_token",
"message": "Invalid or expired access token."
}
}

Fine-grained codes help you find the error right away: invalid_token (missing, malformed or invalid signature), invalid_audience (token for another Kobana app), token_revoked and unknown_organization — the latter is a 403: the token is valid, the organization just isn't here.

If our Authorization Server is down, the response is 5xx, not 401. This is deliberate: a 401 would send you debugging a perfectly good credential during our outage. Try again in a moment.

:::note sub is not the tenant sub is the person and org is the organization — they are uuids from different tables, so swapping them raises no format error. A token without org is refused instead of falling back to sub: that fallback would match no organization and answer 403 to everything, with the symptom pointing to permissions rather than to the claim that was read. :::

Scopes​

Token scopes have two orthogonal axes: the resource, always prefixed by the app (dashboards/users), and the verb, a global scope without prefix (write). A resource without a verb means read. Writing requires both: the resource scope and the verb.

ScopeWhat it reaches
dashboards/builderPortal builder: pages, draft, publishing and versions
dashboards/usersPortal users and the mapping that creates them
dashboards/portalThe portal: access, branding, look, SEO, tracking, domains and sign-in providers
dashboards/connectorsConnectors (data sources)
dashboards/entitiesEntities and their records
dashboards/assetsFile library
dashboards/aiAI usage and billing
dashboards/workspacesWorkspaces
dashboards/membersDashboard members, roles and permissions
dashboards/adminOrganization and settings, notifications and e-mail templates, events, webhooks, audit, API keys, backups, exports, imports, usage and rate limits
Resource wildcarddashboards/all — reads all the resources above, and grants no verb
Global verbwrite — every write (POST, PUT, PATCH, DELETE) on the resources the token carries

Two examples: dashboards/users reads portal users and does not write; dashboards/users write also invites, updates and deletes them, and still does not reach the dashboard members.

The granular form dashboards/users:write is an exception in the grammar, not the default format: it satisfies both axes for that one resource, and exists for "read one resource, write another". dashboards/* is a registration wildcard and is never issued in a token — matching here is always literal. There is no read scope: reading is the resource without a verb.

:::info Scopes are not enforced yet The scopes above are already this API's vocabulary, but they are not enforced yet: they first need to be registered in the Authorization Server, and until then no token in circulation carries a scope — enforcing now would either allow everything (theater) or block every live integration. Today what binds the token to this API is the aud, which the verifier requires. Once enforcement starts, a missing scope answers 403 insufficient_scope; ask for the minimum scope of each integration now so you don't need to change it later. :::

Best practices​

  • Never commit tokens to git. Use environment variables or vaults (AWS Secrets Manager, Vault, etc.).
  • Use separate tokens per integration (CRM, ERP, frontend, etc.) — it makes revoking one easy without taking down the others, and auditing by jti stays readable.
  • Ask for the minimum scopes needed (principle of least privilege) — a resource without a verb when the integration only reads.
  • Revoke any leaked token immediately: revocation is by jti and applies on the next call, without waiting for exp.
  • HTTPS is mandatory — HTTP calls are rejected.