Apps & API Keys
User bearer tokens (from login) authenticate a person. When you need a script, server, or integration to call the Lexabit API on its own — with no human signing in — you use an App authenticated by an API key.
What is an App?
An App is a machine actor — a first-class identity in Lexabit that authenticates with a key instead of a user session, and acts within the permission model exactly like a user does. It's not a special mode bolted onto a user account; it's its own entity, with its own roles on a scope.
Two things define an App:
- It holds scoped access to one entity. An App is created on a scope (a tenant, team, …) and holds roles there. Its access is exactly what those roles grant on that entity — nothing wider.
- It authenticates with a token, not a password. You present an API key (or a
client-credentials-minted token) in the
Authorizationheader; there's no user session, no login screen.
Don't confuse three things: your application is your own software; an App (this page) is the Lexabit identity it authenticates as; and a Connector (next page) is a partner's OAuth doorway that mints an App when a customer authorizes it. This page is about first-party Apps — ones you create yourself for your own tenant.
Typical use: an internal script or an ERP system that creates clients or pulls scoring results on your tenant's behalf, without impersonating a user.
Set up an App and create a key
You create an App as a signed-in user who has the apps.create permission on the
scope. The scope you pass (X-Scope, the scopeId
from GET /scopes/v1) is the entity the App will hold access on.
- curl
- Python
- JavaScript
curl -X POST "https://api.lexabit.com/apps/v1" \
-H "Authorization: Bearer $LEXABIT_TOKEN" \
-H "X-Scope: 42" \
-H "Content-Type: application/json" \
-d '{
"name": "ERP Sync",
"roles": ["member"],
"createKey": true,
"keyName": "erp-sync-prod"
}'
import os, requests
resp = requests.post(
"https://api.lexabit.com/apps/v1",
headers={
"Authorization": f"Bearer {os.environ['LEXABIT_TOKEN']}",
"X-Scope": "42",
},
json={"name": "ERP Sync", "roles": ["member"], "createKey": True, "keyName": "erp-sync-prod"},
)
resp.raise_for_status()
app = resp.json()["data"]
api_key = app["key"]["plainTextToken"] # shown ONCE — store it now
const resp = await fetch("https://api.lexabit.com/apps/v1", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LEXABIT_TOKEN}`,
"X-Scope": "42",
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "ERP Sync", roles: ["member"], createKey: true, keyName: "erp-sync-prod" }),
});
const app = (await resp.json()).data;
const apiKey = app.key.plainTextToken; // shown ONCE — store it now
201 Created:
{
"data": {
"id": "0a1b2c3d-…",
"name": "ERP Sync",
"status": "active",
"key": { "plainTextToken": "1|abcdef123456…" }
},
"meta": { "requestId": "…" }
}
plainTextToken appears only in this response — it's never stored or returned
again (GET /apps/v1/{id} never includes it). Copy it into your secrets manager
immediately. If it's lost, rotate the key; you can't
retrieve it.
The roles are granted on the scope you passed, from the same role catalog users
get (admin/manager/member/viewer/…). You can't grant an App more than you
hold yourself — see the ceiling rule.
Call the API as the App
Use the key as a normal bearer token, with the scope the App has access to:
curl "https://api.lexabit.com/clients/v1" \
-H "Authorization: Bearer 1|abcdef123456…" \
-H "X-Scope: 42"
To verify a key is live and see which App it belongs to, call whoami (works for any token, needs no scope or permission):
curl "https://api.lexabit.com/apps/v1/whoami" \
-H "Authorization: Bearer 1|abcdef123456…"
{ "data": { "actorType": "app", "actorId": "0a1b2c3d-…", "name": "ERP Sync", "status": "active" } }
Rotate and revoke keys
An App can hold at most 2 active keys — enough to rotate without downtime: issue a new key, cut your integration over, then revoke the old one.
# Issue an additional key (for overlap during rotation)
curl -X POST "https://api.lexabit.com/apps/v1/{appId}/keys" \
-H "Authorization: Bearer $LEXABIT_TOKEN" -H "X-Scope: 42" \
-H "Content-Type: application/json" -d '{ "keyName": "erp-sync-2026" }'
# Revoke a key you no longer need
curl -X DELETE "https://api.lexabit.com/apps/v1/{appId}/keys/{keyId}" \
-H "Authorization: Bearer $LEXABIT_TOKEN" -H "X-Scope: 42"
POST /apps/v1/{appId}/keys/{keyId}/rotate is the shortcut: it returns a new
plaintext token and immediately kills the old one (no grace period). A third
active key returns 422 max_keys_exceeded.
Client credentials (OAuth2) — the alternative to a static key
If your tooling already speaks OAuth, an App can instead use client
credentials: a client_id + client_secret you exchange for a short-lived
bearer token (standard machine-to-machine OAuth, RFC 6749 §4.4). Both approaches
resolve to the same App — pick whichever fits your stack.
# 1. Create the credential set (once). clientSecret is shown ONCE, like a key.
curl -X POST "https://api.lexabit.com/apps/v1/{appId}/client-credentials" \
-H "Authorization: Bearer $LEXABIT_TOKEN" -H "X-Scope: 42"
# → { "data": { "clientId": "app_9f3c…", "clientSecret": "wq8Z…" } }
# 2. Exchange them for a token at the OAuth token endpoint (RFC-flat response).
curl -X POST "https://api.lexabit.com/oauth/v1/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=app_9f3c…&client_secret=wq8Z…"
# → { "access_token": "1|9x7K…", "token_type": "Bearer", "expires_in": 3600 }
# 3. Call the API with the minted token (re-fetch when it expires, ~60 min).
curl "https://api.lexabit.com/clients/v1" \
-H "Authorization: Bearer 1|9x7K…" -H "X-Scope: 42"
The token endpoint returns the RFC-flat shape (not the {data,meta} envelope)
so generic OAuth libraries can parse it. POST /apps/v1/{appId}/client-credentials/rotate
replaces the secret; DELETE revokes the set. Only one credential set per App.
Roles & the ceiling rule
Grant or revoke an App's roles after creation:
curl -X POST "https://api.lexabit.com/apps/v1/{appId}/roles" \
-H "Authorization: Bearer $LEXABIT_TOKEN" -H "X-Scope: 42" \
-H "Content-Type: application/json" \
-d '{ "entityType": "tenant", "entityId": "<tenant-uuid>", "roleKey": "manager" }'
Every grant is ceiling-checked: you can only give an App permissions you
already hold on that entity. A grant that would exceed your own access fails with
403 ceiling_violation and a list of the missing permissions — nothing is created.
Suspend (kill switch)
Suspending an App instantly stops every key and minted token from authenticating — no cache to wait out — without deleting anything. Reactivating resumes them immediately.
curl -X PATCH "https://api.lexabit.com/apps/v1/{appId}" \
-H "Authorization: Bearer $LEXABIT_TOKEN" -H "X-Scope: 42" \
-H "Content-Type: application/json" -d '{ "status": "suspended" }'
Deleting the App (DELETE /apps/v1/{appId}) goes further: it revokes all keys,
removes the App's roles everywhere, and soft-deletes it.
Where to go next
- Connectors & OAuth — let a third-party app connect to your customers' Lexabit data with their consent.
- Connecting an MCP — connect an MCP client.
- API Reference → Apps & API Keys — every endpoint and field.