# lema docs — API: keys, usage & identity

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

### 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_id` (string (UUID), required) — The org the key is bound to.
- `name` (string) — A label for the key (max 100 characters).
- `scopes` (string[]) — decisions:read and/or decisions:write. Omitted → read-only. A scope above your role in the org is refused.
- `expires_in_days` (integer) — 0 (never — the default) to 366.

Response:
- `key` (string) — The full lema_live_ key — shown exactly once, never retrievable again.
- `id` (string (UUID)) — The key id, for revocation.
- `org_id` (string (UUID)) — The org it answers over.
- `name` (string) — Your label.
- `lookup / last4` (string) — The masked identifiers the list endpoint shows.
- `scopes` (string[]) — What the key can do.
- `created_at` (string (RFC3339)) — When it was minted.
- `expires_at / last_used_at / revoked_at` (string (RFC3339) · optional) — Lifecycle timestamps, present once they apply.

Errors:
- 400 — missing org_id, over-long name, unknown scope, or expires_in_days outside 0–366.
- 403 — called with an API key, or the requested scopes exceed your role in the org.
- 404 — the org does not exist — or is not yours; the two read the same.
- 409 — you have no workspace in this org yet — connect a repo first, or the key would answer over nothing.
- 501 — API 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.

Example:

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

```
{
  "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 — 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:
- `keys` (object[]) — Masked key records — the mint response minus the key field.

Errors:
- 403 — called with an API key.
- 501 — API keys are not configured on this deployment.

Example:

```
curl https://api.lema.sh/api-keys \
  -H "Authorization: Bearer <session JWT>"
```

```
{
  "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} — 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:
- `keyID` (path (UUID), required) — The key id from mint or list.

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

> Success is 204 No Content — an empty body.

Example:

```
curl -X DELETE https://api.lema.sh/api-keys/<key uuid> \
  -H "Authorization: Bearer <session JWT>"
```

```
204 No Content
```

### 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:
- `plan` (string) — free or pro.
- `mcp_queries` (object) — { used, limit } — the summed daily wallet. limit is JSON null when unlimited.
- `breakdown` (object) — { asks, retrieves } — how the wallet was spent.
- `imports` (object) — { used, limit } — daily imports.
- `captures` (object) — { used, limit } — daily decision captures.
- `resets_at` (string (RFC3339)) — Next UTC midnight — when every daily meter resets.

Example:

```
curl https://api.lema.sh/usage \
  -H "Authorization: Bearer $LEMA_API_TOKEN"
```

```
{
  "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 — 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_id` (string (UUID)) — The user the credential belongs to.
- `org_id` (string (UUID)) — The org it operates in.
- `email` (string) — The account email.
- `scopes` (string[]) — What this credential may do.
- `org_role` (string) — Advisory only — every write is enforced server-side regardless of what this says.
- `org_member_count` (integer) — How many people are in the org.

Example:

```
curl https://api.lema.sh/me \
  -H "Authorization: Bearer $LEMA_API_TOKEN"
```

```
{
  "user_id": "<uuid>",
  "org_id": "<uuid>",
  "email": "you@example.com",
  "scopes": ["decisions:read"],
  "org_role": "member",
  "org_member_count": 1
}
```
