Skip to main content

API Conventions

Conventions that apply to every endpoint — the response envelope, scoping, pagination, filtering, and errors. Read this once and the rest of the reference reads the same way.

Base URLs & environments​

EnvironmentBase URLUse it for
Staginghttps://api.staging.klart.comAll development, testing, and integration work.
Productionhttps://api.klart.comProduction workloads only — applications running against the live environment.

Code examples throughout these docs show the production host. While you are developing or testing — including building a new integration — substitute api.staging.klart.com. The two environments expose the same API but hold entirely separate data: accounts, tenants, connectors, client credentials, and tokens are not shared between them, so staging credentials never work against production (and vice versa).

Each environment publishes its own RFC 8414 discovery document at /.well-known/oauth-authorization-server, so OAuth clients can resolve the correct endpoints from the base URL alone.

The response envelope​

Every response — success or error — is a JSON object that always contains a top-level meta.

On success (2xx) the payload is under data:

{
"data": { "id": "…", "name": "Acme AS" },
"meta": { "requestId": "3f1c…" }
}

For a list, data is an array and meta also carries pagination (see below).

On failure (4xx/5xx) the payload is under error:

{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": { "email": ["The email field is required."] }
},
"meta": { "requestId": "3f1c…" }
}
  • data is present only on success; error only on failure. Both always include meta.
  • meta.requestId uniquely identifies the request — include it when contacting support.
Always read the envelope, never the bare body

Whatever language you use, unwrap data on success and error on failure. Don't assume the resource is at the top level — it never is.

Authentication​

All endpoints (except login, registration, and password reset) require a bearer token:

Authorization: Bearer <token>

See Authentication for how to obtain one.

Platform config — the one read that needs no token​

GET /platform/v1/config is unauthenticated, because a client has to render the platform's name and logo before anyone signs in, and needs to know the platform is in maintenance without first making a call that would fail:

curl "https://api.klart.com/platform/v1/config"
{
"data": {
"name": "Klart",
"logoUrl": "https://…/logo.svg",
"logoInverseUrl": "https://…/logo-inverse.svg",
"faviconUrl": "https://…/favicon.ico",
"defaultLocale": "no",
"maintenanceMode": false
},
"meta": { "requestId": "3f1c…" }
}

logoInverseUrl is the light-on-dark variant, for dark backgrounds; either URL may be null if the operator has not uploaded that variant. Check maintenanceMode before a sign-in attempt and show the operator's own name rather than hardcoding a brand.

The payload is a fixed allowlist, not a filtered dump of platform settings — new settings stay private unless they are explicitly added to it, so nothing operational or customer-related will ever appear here. It is rate-limited under the standard api bucket like every other endpoint.

Scope — the X-Scope header​

Most endpoints operate within a scope — the tenant, team, or other entity you're acting in. Send its scopeId with every scoped request:

X-Scope: <scopeId>

You get the scopeId from GET /scopes/v1, which lists the scopes you can act in. Treat scopeId as an opaque token — send it back exactly as received; don't build or parse it, and note it's not the same as the entity's own id.

If you omit X-Scope on a scoped endpoint, the request fails with 400 and error.code = "bad_request" ("scope not set"). The scope also determines what you can see: list endpoints only return records visible from the current scope. How to list your scopes and how they nest is covered in Concepts → Scopes & Workspaces.

Pagination​

List endpoints are paginated. Set the page size with perPage and navigate with page:

GET /clients/v1?perPage=25&page=2

List responses include a pagination block in meta:

"meta": {
"requestId": "3f1c…",
"pagination": {
"total": 137,
"perPage": 25,
"currentPage": 2,
"lastPage": 6,
"links": { "first": "…", "prev": "…", "next": "…", "last": "…" }
}
}
Cursor pagination

A few high-volume endpoints (company search, account transactions, counterparts) use cursor pagination instead of page numbers: pass perPage and a cursor, and follow the nextCursor value from meta until it's null. The reference marks which style each endpoint uses.

Sorting​

sort takes a comma-separated list of fields; prefix a field with - for descending:

GET /clients/v1?sort=-createdAt,name

Each endpoint allowlists which fields can be sorted.

Filtering​

Filter with filter[field]=value:

GET /clients/v1?filter[status]=active

Each endpoint allowlists which fields can be filtered.

Idempotency​

POST requests can be made safely retryable by sending a unique Idempotency-Key:

Idempotency-Key: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed

Retrying with the same key returns the original result instead of creating a duplicate; reusing a key with a different body returns 409. See Error Handling for a retry pattern that uses this.

Errors & status codes​

Errors use the error envelope above. error.code is a stable machine-readable string:

Statuserror.codeMeaning
400bad_requestMalformed request (e.g. missing X-Scope)
401unauthenticatedMissing or invalid token
403forbiddenAuthenticated, but you lack the required permission
404not_foundThe resource doesn't exist or isn't visible in your scope
409conflictState conflict (including idempotency mismatches)
422validation_failedInvalid input — error.details maps each field to its messages
429rate_limitedToo many requests
500server_errorUnexpected server error

Always branch on error.code (not the message text, which may change). For 422, read error.details for per-field validation messages. The Error Handling guide shows this in code.

Putting it together​

A complete, scoped, authenticated call looks the same in every language — set two headers, then read data. Here we list the first page of clients:

curl "https://api.klart.com/clients/v1?perPage=2&sort=-createdAt" \
-H "Authorization: Bearer $KLART_TOKEN" \
-H "X-Scope: 42"

A successful 200 response:

{
"data": [
{
"id": "9b1d…",
"type": "company",
"displayName": "Acme AS",
"countryCode": "NO",
"registrationNumber": "922020175",
"status": "active",
"createdAt": "2026-05-14T09:12:03Z"
},
{
"id": "7a44…",
"type": "person",
"displayName": "Kari Nordmann",
"countryCode": "NO",
"status": "prospect",
"createdAt": "2026-05-13T16:40:55Z"
}
],
"meta": {
"requestId": "3f1c8e2a-…",
"pagination": {
"total": 137,
"perPage": 2,
"currentPage": 1,
"lastPage": 69,
"links": { "first": "…", "prev": null, "next": "…", "last": "…" }
}
}
}

From here, the whole API is variations on this shape. The Quick Start walks the very first call step by step, and each guide applies these conventions to a real workflow.