lema · docs · api

Keys, usage & identity

The account surface. Key lifecycle is session-only by design — a lema_live_ key can never mint, list, or revoke keys, so a leak never outruns revocation. The two reads — /usage and /me — work with any authenticated key.

POST

/api-keys

POST/api-keys

Mint a key

Mints a lema_live_ key bound to one org. Session-only by design: an API key calling this gets a 403 — a leaked key must never be able to mint its own successors, or revocation means nothing. The full key string is returned exactly once, here, and never again.

auth · Browser session only — an API key gets 403.

Request body

org_idstring (UUID)required
The org the key is bound to.
namestring
A label for the key (max 100 characters).
scopesstring[]
decisions:read and/or decisions:write. Omitted → read-only. A scope above your role in the org is refused.
expires_in_daysinteger
0 (never — the default) to 366.

Response

keystring
The full lema_live_ key — shown exactly once, never retrievable again.
idstring (UUID)
The key id, for revocation.
org_idstring (UUID)
The org it answers over.
namestring
Your label.
lookup / last4string
The masked identifiers the list endpoint shows.
scopesstring[]
What the key can do.
created_atstring (RFC3339)
When it was minted.
expires_at / last_used_at / revoked_atstring (RFC3339) · optional
Lifecycle timestamps, present once they apply.

Errors

  • 400missing org_id, over-long name, unknown scope, or expires_in_days outside 0–366.
  • 403called with an API key, or the requested scopes exceed your role in the org.
  • 404the org does not exist — or is not yours; the two read the same.
  • 409you have no workspace in this org yet — connect a repo first, or the key would answer over nothing.
  • 501API keys are not configured on this deployment.

Success is 201 Created.

The practical path is the UI at lema.sh/account/api-keys — same contract, same one-time reveal.

$ curl https://api.lema.sh/api-keys \
  -H "Authorization: Bearer <session JWT — not an API key>" \
  -H "Content-Type: application/json" \
  -d '{"org_id": "<org uuid>", "name": "ci-reader",
       "scopes": ["decisions:read"], "expires_in_days": 90}'
response shape
{
  "id": "<key uuid>",
  "org_id": "<org uuid>",
  "name": "ci-reader",
  "lookup": "<masked>",
  "last4": "<masked>",
  "scopes": ["decisions:read"],
  "created_at": "<RFC3339>",
  "expires_at": "<RFC3339>",
  "key": "lema_live_<shown exactly once>"
}

GET

/api-keys

GET/api-keys

List keys

Your keys, masked: name, lookup fragment, last4, scopes, and lifecycle timestamps. The full key string is never listable — if you lost it, revoke and re-mint.

auth · Browser session only — an API key gets 403.

No request body.

Response

keysobject[]
Masked key records — the mint response minus the key field.

Errors

  • 403called with an API key.
  • 501API keys are not configured on this deployment.
$ curl https://api.lema.sh/api-keys \
  -H "Authorization: Bearer <session JWT>"
response shape
{
  "keys": [
    {
      "id": "<key uuid>",
      "org_id": "<org uuid>",
      "name": "ci-reader",
      "lookup": "<masked>",
      "last4": "<masked>",
      "scopes": ["decisions:read"],
      "created_at": "<RFC3339>",
      "last_used_at": "<RFC3339>"
    }
  ]
}

DELETE

/api-keys/{keyID}

DELETE/api-keys/{keyID}

Revoke a key

Soft-revokes one key by id. A revoked key stops verifying immediately; the record stays listed with its revoked_at set.

auth · Browser session only — an API key gets 403.

Parameters

keyIDpath (UUID)required
The key id from mint or list.

Errors

  • 400keyID is not a UUID.
  • 403called with an API key.
  • 404no such key — or not yours; the two read the same.
  • 501API keys are not configured on this deployment.

Success is 204 No Content — an empty body.

$ curl -X DELETE https://api.lema.sh/api-keys/<key uuid> \
  -H "Authorization: Bearer <session JWT>"
on success
204 No Content

GET

/usage

GET/usage

Where you stand

Your plan and the live meters: the unified MCP-query wallet (asks + retrieves, with the breakdown), imports, captures, and when everything resets. This endpoint never charges the wallet — it IS the meter.

auth · decisions:read

No request body.

Response

planstring
free or pro.
mcp_queriesobject
{ used, limit } — the summed daily wallet. limit is JSON null when unlimited.
breakdownobject
{ asks, retrieves } — how the wallet was spent.
importsobject
{ used, limit } — daily imports.
capturesobject
{ used, limit } — daily decision captures.
resets_atstring (RFC3339)
Next UTC midnight — when every daily meter resets.
$ curl https://api.lema.sh/usage \
  -H "Authorization: Bearer $LEMA_API_TOKEN"
response shape
{
  "plan": "pro",
  "mcp_queries": { "used": …, "limit": 2500 },
  "breakdown": { "asks": …, "retrieves": … },
  "imports": { "used": …, "limit": 50 },
  "captures": { "used": …, "limit": 500 },
  "resets_at": "<RFC3339 — next UTC midnight>"
}

GET

/me

GET/me

Who the key is

Echoes the verified principal: the user and org the credential resolves to, its scopes, and the org member count. Useful as the first call in any integration — it proves the key works before you build on it.

auth · Any authenticated caller — no scope required.

No request body.

Response

user_idstring (UUID)
The user the credential belongs to.
org_idstring (UUID)
The org it operates in.
emailstring
The account email.
scopesstring[]
What this credential may do.
org_rolestring
Advisory only — every write is enforced server-side regardless of what this says.
org_member_countinteger
How many people are in the org.
$ curl https://api.lema.sh/me \
  -H "Authorization: Bearer $LEMA_API_TOKEN"
response shape
{
  "user_id": "<uuid>",
  "org_id": "<uuid>",
  "email": "[email protected]",
  "scopes": ["decisions:read"],
  "org_role": "member",
  "org_member_count": 1
}