API keys

Key lifecycle, the exact reach of each scope, rotation without downtime, and how to audit every credential.
View as Markdown

A workspace API key is the credential your servers, scripts and integrations use to call the Jelliu REST API and the MCP server. This page is the operational deep dive: how a key is born, what each scope can and cannot reach route by route, how revocation propagates, how to rotate with zero downtime, and how to audit who did what with which key.

For the header format and the basic error responses, start with Authentication.

How it works

Every request passes three independent gates, in this order:

  1. Authentication. The bearer value must be jl_ followed by exactly 64 lowercase hex characters. Jelliu hashes it with SHA-256 and looks it up. Unknown, revoked or expired keys, and keys whose workspace was deleted, are refused with 401.
  2. Scope by HTTP method. Applied to every /api request. GET, HEAD and OPTIONS need read, write or full. POST, PUT, PATCH and DELETE need write or full.
  3. Role gate per route. Routes restricted to workspace roles check the key again. On routes that no ordinary member can use (admin, owner or billing only), only a full key passes.

Some credential-management routes add a fourth gate that refuses every API key, including full. See Dashboard-only operations.

Three kinds of credential

Jelliu issues three unrelated credentials. They are not interchangeable, and each one is accepted only where listed.

CredentialFormatSent asAccepted byCreated by
Workspace API keyjl_ + 64 hexAuthorization: Bearer jl_...https://api.jelliu.co/api/* and https://mcp.jelliu.coWorkspace owner, in Settings → API Keys
Zapier keyzk_ + 64 hexx-zapier-api-key: zk_...Only the Zapier endpoints under /zapierWorkspace owner, from the dashboard. See Zapier
Widget API keyOpaque secret, one per widgetx-widget-api-keyOnly the public /widget/* endpoints of that widgetReturned once when the widget is created. See Web chat

None of these belongs in a browser, a mobile app bundle or a public repository. The only identifier that is safe to publish is a widget’s ID (a UUID), which the embed snippet uses.

The key object

GET /api/api-keys returns the workspace’s non-revoked keys, newest first. The secret is never included; only its public prefix.

{
"data": [
{
"id": "3f6c2a8e-1b7d-4e5f-9a0c-2d4e6f8a1b3c",
"tenant_id": "9b2e4d6f-8a1c-4e3b-a5d7-c9e1f3a5b7d9",
"agent_id": null,
"name": "CRM sync (production)",
"prefix": "jl_0f9e8d7c",
"scopes": ["read", "write"],
"created_by": "user_2abcDEFghiJKLmnoPQR",
"last_used_at": "2026-09-14T15:41:02.118Z",
"expires_at": null,
"revoked_at": null,
"created_at": "2026-09-01T12:00:00.000Z",
"usable": true
}
]
}
FieldTypeDescription
iduuidKey identifier. Used to revoke, and appears in the audit log as apikey: followed by this id.
namestringLabel chosen at creation, 1 to 200 characters.
prefixstringjl_ plus the first 8 hex characters of the secret. Enough to recognize a key in your secret store; not enough to use it.
scopesstring[]Any of read, write, full.
agent_iduuid or nullAlways null for keys you create. See Keys that belong to an agent.
created_bystring or nullThe dashboard user who created the key.
last_used_attimestamp or nullLast successful authentication. Updated at most once per minute per key, so treat it as approximate.
expires_attimestamp or nullnull means the key never expires.
revoked_attimestamp or nullAlways null in this list: revoked keys are not returned.
usablebooleanWhether the key can authenticate right now. An expired key is still listed, with usable: false, so you can find and clean it up.

Listing keys is an owner-level route. A full key can call it; read and write keys receive 403 with This operation requires an API key with the 'full' scope.

Lifecycle

1

Create

The workspace owner creates the key in the dashboard under Settings → API Keys. Admins cannot, and no API key can create another key.

SettingRules
NameRequired, 1 to 200 characters after trimming. Cannot start with internal:, which is reserved for keys Jelliu issues itself.
ScopesAt least one of read, write, full. If none is chosen the default is read + write. full is never a default: it must be selected explicitly.
ExpiryOptional. Without one, the key never expires. A date you choose is honored exactly, with no maximum, and must be in the future.

The plaintext key is displayed once. Jelliu stores only its SHA-256 hash and cannot show it again. If you lose it, create a new key and revoke the old one.

2

Use

Send it on every request as Authorization: Bearer jl_.... Every key of the workspace shares the same rate limits, because limits are counted per workspace.

3

Expire (optional)

A key with an expires_at stops authenticating at that moment. Expiry is re-checked even when the validation result is cached, so a key does not keep working past its date. It stays in the list with usable: false until you revoke it.

4

Revoke

The owner revokes the key from the dashboard. Revocation is a soft delete (the record is kept for audit history) and frees a slot under the 25-key limit. The instance that handles the revocation evicts the key from its caches immediately; other instances stop accepting it within about 10 seconds. Revoking a key that is already revoked or does not exist returns 404 with API key not found or already revoked.

Keys also stop working when the workspace is deleted.

Limits on keys

LimitValueError
Active keys per workspace25409 PLAN_LIMIT_EXCEEDED: Maximum 25 active API keys per tenant. Revoke old keys before creating new ones.
Scopes per key1 to 20 entries, each read, write or full400 VALIDATION_FAILED
Name length1 to 200 characters400 VALIDATION_FAILED
Expiry in the pastRefused400 VALIDATION_FAILED: expires_at must be in the future — a key that is born expired cannot authenticate.

Expired keys that have not been revoked still count toward the 25. Revoke them to free the slot.

What each scope can reach

The matrix below is derived from the gates applied to each route. “Member-level” routes are those a workspace member can use in the dashboard; “admin-exclusive” routes are those only an admin, owner or billing user can use.

Operationreadwritefull
Read agents, campaigns, contacts, calls, conversations, phone numbers, widgets, webhooks, analytics and other member-level resourcesYesYesYes
Create and update agents, add contacts (single, bulk, CSV), place calls, send WhatsApp and email, upload knowledge documents, manage agent tools, widgets and MCP serversNoYesYes
Create, update, delete, activate and pause campaignsNoNoYes
Create, update and delete outbound webhooksNoNoYes
Delete agentsNoNoYes
Provision phone numbers or connect a SIP trunkNoNoYes
Create, submit, import and delete WhatsApp templates; manage WhatsApp sendersNoNoYes
Create, update, test and delete integrations; everything under /api/composioNoNoYes
Live call transfer (/api/calls/{callId}/transfer)NoNoYes
Billing reads: usage, limits, invoices, charges, payment methodsNoNoYes
Billing purchases: checkout, plan change, minute packs, agent add-ons, phone numbersNoNoYes
Audit log (/api/audit) and the agent action ledger (/api/agent-actions)NoNoYes
Compliance changes: record or withdraw consent, import, list or delete suppressions, set retention, erase a contact, change country rulesNoNoYes
Email sending settings (PATCH /api/email/settings, PUT /api/email/settings/sending)NoNoYes
List API keysNoNoYes
Create or revoke API keys, Zapier keys; rotate a webhook secretNoNoNo

A full key is treated as a workspace administrator and also passes owner-only routes: it can spend money on the saved card, read the audit log and erase contact data. Give it only to automations that genuinely manage the workspace, and store it like an admin password.

How a refusal looks

A key that lacks the method’s scope:

{
"error": {
"code": "FORBIDDEN",
"message": "API key lacks the 'write' scope required for this operation"
}
}

A read or write key on an admin-exclusive route:

{
"error": {
"code": "FORBIDDEN",
"message": "This operation requires an API key with the 'full' scope"
}
}

Dashboard-only operations

These routes refuse every API key, whatever its scope, so a leaked key can never mint new credentials or switch off the controls meant to contain a leak:

OperationRoute
Create or revoke an API keyPOST /api/api-keys, DELETE /api/api-keys/{id}
Create or revoke a Zapier keyPOST /api/zapier/api-key, DELETE /api/zapier/api-key/{id}
Rotate a webhook signing secretPOST /api/webhooks/{webhookId}/rotate-secret
Change the workspace sign-in policy, role mappings or SSO; transfer ownershipPUT /api/identity/policy, PUT /api/identity/role-mappings, DELETE /api/identity/role-mappings/{id}, PUT /api/identity/sso, POST /api/identity/ownership/transfer
Change agent change-approval or agent action-approval policyPUT /api/settings/agent-change-approval, PUT /api/agent-approvals/policy
Pin the data residency regionPUT /api/settings/data-residency

The response is 403 FORBIDDEN:

{
"error": {
"code": "FORBIDDEN",
"message": "This action requires a signed-in admin session — API keys cannot perform credential-management operations."
}
}

Workspace sign-in policies do not apply to keys

If the workspace requires MFA or SSO for its members, that requirement applies to people signed in to the dashboard. API keys are machine credentials and pass through it, so hardening your members’ sign-in never breaks integrations.

Keys that belong to an agent

Jelliu also issues credentials for its own use: the tool gateway your voice and chat agents call during a conversation, and the short-lived access tokens of MCP OAuth connections. These credentials:

  • are named with the reserved internal: prefix, are hidden from GET /api/api-keys and do not count toward the 25-key limit;
  • when bound to a specific agent, can never carry the full scope, and stop authenticating as soon as that agent is deleted;
  • for MCP OAuth, expire after one hour and never exceed the role of the person who approved the connection.

You cannot create, list or revoke them through the API. That is why agent_id is always null on the keys you manage.

Rotating a key without downtime

A workspace can hold up to 25 keys at once, and every key works independently, so rotation is a matter of overlapping two keys.

1

Create the replacement

In Settings → API Keys, create a new key with the same scopes. Give it a name that identifies the rotation, such as CRM sync (2026-09).

2

Deploy it everywhere

Update the secret in your secret manager and roll out every process that uses it. Both keys authenticate during this window.

3

Confirm the old key is idle

List the keys with a full key and check that the old key’s last_used_at has stopped moving. Because the timestamp is updated at most once a minute, wait a few minutes after the last deploy before trusting it.

curl -sS "https://api.jelliu.co/api/api-keys" \
-H "Authorization: Bearer $JELLIU_ADMIN_API_KEY"

Expected output:

jl_0f9e8d7c idle for 14.2 minutes
4

Revoke the old key

Revoke it from the dashboard. Allow about 10 seconds for every instance to refuse it. Any process you missed will start receiving 401 UNAUTHORIZED with Invalid or revoked API key.

Jelliu never forces rotation: a key without an expiry lives until you revoke it. If your security policy requires periodic rotation, set expires_at when you create the key and put the date in your calendar, then rotate before it arrives. An expired key fails immediately with 401, with no grace period.

If a key leaks

  1. Revoke it first, from Settings → API Keys. Do not wait until the replacement is deployed if the key was exposed publicly.
  2. Create a replacement and deploy it.
  3. Review what the key did. Every audited request records the key as apikey:KEY_ID (see below).
  4. If the key had full scope, also review outbound webhooks, WhatsApp templates, integrations and billing activity, which only full keys can change.

Auditing keys

Creation and revocation events

Creating and revoking a key each write an audit record with resource_type api_key and action api_key.create or api_key.revoke. The record carries the key’s public identity only: its id, prefix, scopes and expiry, who issued or revoked it, and, on creation, admin_equivalent: true when the key has the full scope. The secret and its hash are never written to the audit log.

curl -sS "https://api.jelliu.co/api/audit?resourceType=api_key&limit=20" \
-H "Authorization: Bearer $JELLIU_ADMIN_API_KEY"
{
"data": [
{
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"tenant_id": "9b2e4d6f-8a1c-4e3b-a5d7-c9e1f3a5b7d9",
"user_id": "user_2abcDEFghiJKLmnoPQR",
"action": "api_key.create",
"resource_type": "api_key",
"resource_id": "3f6c2a8e-1b7d-4e5f-9a0c-2d4e6f8a1b3c",
"ip_address": "203.0.113.24",
"user_agent": { "browser": "Chrome", "os": "macOS", "device": "desktop" },
"created_at": "2026-09-01T12:00:00.000Z"
}
]
}

The list omits the changes payload. Fetch one entry with GET /api/audit/{id} to see the prefix, scopes and expiry that were recorded. limit accepts 1 to 100 (default 50) and offset pages through older entries.

What a key did

Audited requests made with an API key are recorded with user_id set to apikey: followed by the key’s id. Filter on it to see a single key’s activity:

curl -sS "https://api.jelliu.co/api/audit?userId=apikey:3f6c2a8e-1b7d-4e5f-9a0c-2d4e6f8a1b3c" \
-H "Authorization: Bearer $JELLIU_ADMIN_API_KEY"

Alert on new keys in real time

Subscribe a webhook to audit.log_recorded with the auditActions filter set to api_key. to receive a signed event whenever a key is created or revoked. Remember that audit.log_recorded is not included in "*" and must be named explicitly. See Webhooks.

curl -sS -X POST "https://api.jelliu.co/api/webhooks" \
-H "Authorization: Bearer $JELLIU_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://siem.example.com/hooks/jelliu",
"events": ["audit.log_recorded"],
"filters": { "auditActions": ["api_key."] },
"description": "Credential changes"
}'

Errors

StatusCodeMessageCause
401UNAUTHORIZEDInvalid or revoked API keyWell-formed key that is unknown, revoked or expired, or whose workspace was deleted.
401UNAUTHORIZEDAuthentication required — sign in with Clerk or provide a Bearer API keyMissing header, or a value that is not jl_ plus 64 lowercase hex characters.
403FORBIDDENAPI key lacks the 'write' scope required for this operationMutation with a read-only key.
403FORBIDDENThis operation requires an API key with the 'full' scopeAdmin-exclusive route with a read or write key.
403FORBIDDENThis action requires a signed-in admin session — API keys cannot perform credential-management operations.A dashboard-only operation.
403TENANT_SUSPENDEDThe workspace is suspended.
404NOT_FOUNDAPI key not found or already revokedRevoking a key that does not exist in the workspace or was already revoked.
409PLAN_LIMIT_EXCEEDEDMaximum 25 active API keys per tenant. Revoke old keys before creating new ones.Creating a 26th key.

Security best practices

  • Least privilege. read for reporting and BI, write for integrations that add contacts or send messages, full only for automation that manages campaigns, webhooks, billing or compliance.
  • One key per integration and environment. Separate keys make last_used_at meaningful, let you revoke one integration without touching the others, and make the audit trail attributable.
  • Keep keys server-side. Never ship a jl_ key to a browser or mobile client. For a website chat, use the widget and its public widget ID.
  • Store keys in a secret manager, inject them as environment variables, and keep them out of logs. The 8-character prefix is enough to identify a key in logs and tickets.
  • Watch for idle and expired keys. Periodically list keys and revoke any whose last_used_at is old or whose usable is false.
  • Alert on credential changes with an audit.log_recorded webhook filtered to api_key..
  • For AI assistants, prefer OAuth. A connector that signs in through MCP OAuth receives one-hour tokens tied to the approving person, instead of a long-lived key pasted into a config file.