Skip to main content

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 Authorization header; there's no user session, no login screen.
App vs. Connector vs. your application

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 -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"
}'

201 Created:

{
"data": {
"id": "0a1b2c3d-…",
"name": "ERP Sync",
"status": "active",
"key": { "plainTextToken": "1|abcdef123456…" }
},
"meta": { "requestId": "…" }
}
The key is shown exactly once

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