Skip to main content

Developer API keys & CLI

For teams that want to script Lira instead of clicking through the dashboard. Any member of the organization can create a scoped API key, and your engineers use it with the Lira CLI or the REST API to connect MCP tools, approve them, and mint support sessions for your customers from your backend.

Test vs live keys

Every Lira key carries a mode, and both modes are valid at the same time. One organization, one dashboard, two key sets — so your staging environment and your production environment can point at the same Lira workspace without ever mixing.

KeyWhere it belongsWhat it does
lira_sk_test_…staging / local backendSecret. Test traffic: its own quota, no real emails, Slack, Linear or webhook deliveries.
lira_sk_live_…production backendSecret. Real customers: real sends, your plan's limits, billing.
lira_pk_test_…staging website / appPublishable — safe in your HTML. Marks that embed's traffic as test.
lira_pk_live_…production website / appPublishable — safe in your HTML. Real customer traffic.

What test mode guarantees:

  • Separate quota. Test conversations and AI replies never consume the volume your live plan is paying for, and hitting a test cap never throttles real customers.
  • No real-world side effects. Outbound email, Slack, Linear and webhook deliveries are suppressed and previewed instead.
  • Out of the live inbox. Test conversations and tickets are only visible when the dashboard is switched to Sandbox (topbar switch), so a staging integration never pollutes the queue your team works.
  • Isolated threads. A test embed cannot resume, read, or hide a live conversation, and vice versa — even for the same customer.

Switch what the dashboard shows with the SANDBOX / PRODUCTION control in the topbar.

Going live does not disturb your test setup

Going live turns on real sends for live-key traffic. Your test keys keep behaving exactly as before.

If a publishable key is wrong

An embed whose publishable key isn't recognised — a typo, the wrong organization, or a key you rotated out — is forced into test mode, never live. That is deliberate: a broken staging deploy must not become live traffic. The widget logs an INVALID_PUBLISHABLE_KEY warning to the browser console with the fix.

An embed that sends no publishable key at all (every embed created before test/live keys existed) keeps following your workspace environment, exactly as it did before. Nothing you already shipped breaks.

Create a key

Any member of the organization can create a key in Settings → Support → API keys → New key — you do not need to be an owner or admin. Developers usually aren't org admins, so requiring it only blocked the people doing the integration.

  1. Give it a name and pick the mode — Test (the default) or Live.
  2. Pick only the permissions it needs.
  3. Optionally set an expiry (defaults to never).
  4. Copy the key when it is shown — it is displayed once and cannot be retrieved again (only revoked).
  5. Use it as the LIRA_API_KEY environment variable. Keep it server-side; never ship it in a mobile app or browser.

Checking what a key can do, from the key itself

If you hold a key and want to know exactly what it is allowed to do — no dashboard login needed:

curl https://api.creovine.com/lira/v1/support/developer-keys/self \
-H "Authorization: Bearer $LIRA_API_KEY"
{
"org_id": "org_xxx",
"key_id": "…",
"name": "Mobile backend",
"scopes": ["sessions:mint"],
"environment": "production",
"status": "active"
}

This is the fastest way to settle "is this key over-scoped?" — it reports what the server actually holds for that key, which is what enforcement uses. It also works as a connectivity and validity check in CI.

Seeing and changing what a key can do

Every key lists its scopes next to it in Settings → Support → API keys, so you can confirm at a glance what each one is allowed to do. Edit changes the name and scopes in place — useful for tightening a key that was granted more than it needed:

  • the key itself does not change, so nothing needs redeploying; the new scopes apply to its next request
  • the environment (sandbox/production) is fixed, because it is part of the token your services already hold — create a new key to switch
  • the secret is never shown again; if it leaked, revoke and create a new one

From the terminal or CI:

lira keys list                       # all keys, with scopes and environment
lira keys show --key-id=<id> # one key in detail
lira keys update --key-id=<id> --scopes=support:read,support:write
lira keys revoke --key-id=<id>

Over the API: GET /support/developer-keys/orgs/{orgId}/keys to read, and PATCH …/keys/{keyId} with { "scopes": [...] } or { "name": "..." } to change. Editing a revoked key returns 409 — create a new one instead.

Publishable keys live in the same place, under Publishable keys — copy them straight into your embed, and rotate either mode independently. Rotating the live key breaks production embeds until you redeploy.

Keys created before test/live mode shipped are shown as Legacy. They keep working and follow your workspace environment. Replace them with explicit test/live keys when convenient.

Permissions (scopes)

ScopeGrants
mcp:readRead your MCP server config and discovered tools.
mcp:writeConnect, approve, enable, and remove MCP tools.
sessions:mintStart a native support session as any of your customers. High privilege — keep this key on your backend only and revoke it if it leaks.
support:readRead support configuration, knowledge-base status, and list knowledge-base documents.
support:writeActivate support, change settings, crawl sites, and manage knowledge-base documents — upload, reprocess, import and delete. Enough to provision and maintain a workspace entirely from CI.

Scopes are enforced on every route that declares them: a key holding only sessions:mint is rejected with 401 and Developer key is missing required scope: support:write on knowledge-base and config endpoints. Grant the narrowest set that works — a session-minting key on a mobile backend should carry sessions:mint alone.

Managing knowledge-base content from CI

support:read and support:write cover the full document lifecycle, so content can be corrected or replaced rather than only added:

# list what is there
curl "https://api.creovine.com/lira/v1/orgs/$ORG/documents" \
-H "Authorization: Bearer $LIRA_API_KEY"

# replace a document
curl -X DELETE "https://api.creovine.com/lira/v1/orgs/$ORG/documents/$DOC_ID" \
-H "Authorization: Bearer $LIRA_API_KEY"
curl -X POST "https://api.creovine.com/lira/v1/orgs/$ORG/documents" \
-H "Authorization: Bearer $LIRA_API_KEY" -F "[email protected]"

Deleting a document that no longer exists returns 404, so a re-run of a cleanup job is safe.

A session minted with a test key is a test session for its whole life, no matter what your workspace environment says. That is how your staging backend produces test traffic while production runs live on the same organization.

Use the CLI

The CLI can switch between test and live itself — you never have to open the dashboard to change which mode you're working in.

npm i -g @liraintelligence/support

lira keys use --api-key=lira_sk_test_… # save each key once
lira keys use --api-key=lira_sk_live_…
lira mode test # switch mode
lira mode live
lira status # org, mode, workspace, and what that means

lira env go-live # move to production (real sends + billing), from the terminal
lira env sandbox

lira channels # what's on: chat, voice, email, portal
lira channels enable voice # turn a channel on
lira channels disable portal

lira docs list # what Lira knows, and whether it finished indexing
lira docs add --file=./handbook.docx # upload a file
lira docs add --text="Refunds take 14 days." --title="Refunds"
lira docs ask "How long do refunds take?" # ask what a customer would ask
lira docs rm <doc_id>

Knowledge base from the terminal

You do not need the dashboard to manage what Lira knows. lira docs add --text is the terminal equivalent of the dashboard's Write a note directly: the text is wrapped in Markdown and stored as an ordinary document, so a note written in the terminal and one written in the dashboard are the same record.

lira docs add --text="Refunds are processed within 14 days." --title="Refunds"
lira docs add --file=./handbook.docx # DOCX, TXT, MD, CSV, XLSX — not PDF
lira docs list # tags, status, chunk counts
lira docs ask "How long do refunds take?" # confirm Lira actually learned it
lira docs rm <doc_id> # deleting frees the slot

Tagging by product, from CI

If one workspace serves several products, brands or regions, tag as you upload and Lira filters retrieval to the customer's product before the AI sees any candidates:

lira docs add --file=./personal-faq.md --segments=personal
lira docs add --file=./pin-reset.md --segments=all # shared, tagged once

lira docs tag <doc_id> --segments=personal,all # re-tag one document
lira docs tag --untagged --segments=all # bulk: anything untagged
lira docs tag --match=corporate --segments=corporate # bulk: by filename
lira docs tag --all --segments=all # bulk: everything

lira docs ask "What do I need to open an account?" --segments=personal

lira docs list shows tags for documents and crawled website pages, and prints how many carry none. Crawled pages obey the same filter, so tagging only documents leaves a workspace that reads "0 untagged" while every page it crawled is about to fall out of retrieval:

lira docs tag --sources --untagged --segments=all   # crawled pages
lira docs tag --sources --match=corporate --segments=corporate

That combined count is the one to drive to zero before switching on kb_segment_strict.

A bulk run that partially fails exits non-zero with the failures named, so CI stops rather than reporting success on a half-tagged knowledge base. Running lira docs tag <doc_id> with no --segments clears the tags, which is how you undo a bulk run.

Priority and cleanup, also from CI:

lira docs authority <doc_id> --level=primary        # answers ahead of everything
lira docs authority --sources --all --level=background
lira docs rm <page_id> --sources # delete a crawled page
lira docs prune # chunks of deleted pages still answering
lira docs prune --yes

Bulk rm dry-runs unless you pass --yes, and prune reports before it deletes — both remove content nothing else can show you.

lira docs ask --segments=personal answers as a Personal customer would be answered — the cheapest end-to-end check that tagging did what you meant, with no session to mint and no widget to open.

lira docs ask is the quickest way to confirm an upload landed — it answers from the knowledge base and names the sources it used, which makes it a useful smoke test at the end of a CI provisioning run. All of it needs a key with support:read / support:write; the same endpoints are documented in the API reference.

Anything the dashboard's Channels tab does is available over the API too — PUT /support/config/orgs/{orgId} with support:write accepts chat_enabled, voice_enabled, email_enabled and portal_enabled — so a workspace can be provisioned end to end from CI without opening the dashboard once.

Using a live key while the CLI is in test mode (or the reverse) is refused with the command to fix it, and a live key does nothing real until the workspace itself has gone live. Full detail: Test and live mode.

LIRA_API_KEY still works and overrides the saved key, for CI:

# Staging
export LIRA_API_KEY=lira_sk_test_…

# Production
# export LIRA_API_KEY=lira_sk_live_…

# Connect and govern an MCP server
lira mcp connect --org-id=org_xxx --endpoint=https://mcp.yourcompany.com/mcp
lira mcp discover --org-id=org_xxx
lira mcp approve --org-id=org_xxx --source-name=order.cancel --risk=customer_confirm
lira mcp enable --org-id=org_xxx

# From your backend, right after the customer authenticated:
lira sessions mint --org-id=org_xxx --email=[email protected]

Or call the API

Every CLI action maps to a REST endpoint. Authenticate with your key as a bearer token.

curl -X POST https://api.creovine.com/lira/v1/support/sessions/orgs/org_xxx/mint \
-H "Authorization: Bearer $LIRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customer": { "email": "[email protected]" }, "ttlSeconds": 900 }'

The response includes a short-lived session token plus the ws_url and rest_base_url your native client uses to open support.

How session minting stays safe

  • Your backend is the trusted party — it vouches for its own logged-in customer, the same way the widget verifies identified visitors.
  • Tokens are short-lived (up to one hour) and can be revoked.
  • For a high-risk action, mint a step-up proof right after the customer re-authenticates (PIN, biometric, or OTP).
  • Requests are rate-limited per key, and each signed request can only be used once (replay-protected).
  • Keys are stored hashed, scoped to one org, and rejected if used against a different org.

Three ways to integrate

SurfaceFor
DashboardHuman admins — click-through setup in Settings → Support.
CLIYour engineers — one-off and scripted setup from a terminal.
APIYour servers / CI — programmatic automation with LIRA_API_KEY.

What each scope unlocks

Every scope below is enforced by the API — a key only does what its scopes allow.

ScopeLets the key…
support:readRead support configuration and knowledge-base status
support:writeActivate support, update settings, crawl a site, upload documents
mcp:readRead the MCP server config and discover tools
mcp:writeConnect, update or remove an MCP server and approve tools
sessions:mintMint a support session token for a logged-in customer

Set up an org from CI, no dashboard

support:write exists so provisioning can live in your pipeline:

ORG=org-xxxx; KEY=lira_sk_...

# 1. activate support
curl -X POST https://api.creovine.com/lira/v1/support/config/orgs/$ORG/activate \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{}'

# 2. configure it
curl -X PUT https://api.creovine.com/lira/v1/support/config/orgs/$ORG \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"greeting_message":"Hi! How can we help?","auto_reply_enabled":true}'

# 3. seed the knowledge base
curl -X POST https://api.creovine.com/lira/v1/orgs/$ORG/crawl \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url":"https://yourcompany.com"}'

A key without the scope gets 401 — a support:read key cannot write.