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.lexabit.comAll development, testing, and integration work.
Productionhttps://api.lexabit.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.lexabit.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.

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.lexabit.com/clients/v1?perPage=2&sort=-createdAt" \
-H "Authorization: Bearer $LEXABIT_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.