OwnLLM Docs
API

Authentication

API keys, scopes, budgets, and rotation for the OwnLLM public API.

Every request to the OwnLLM API needs an API key. Keys are scoped to a single user and a single tenant; they expire on rotation, on user deactivation (manual or via SCIM), and on the optional TTL the user sets at creation.

Key format

sk-ownllm-<24-char-secret>

The sk-ownllm- prefix is visible — it's how we know the key belongs to OwnLLM and route it to the right path. The 24-character secret is the actual credential and is never stored in plain text on our side: we hash it (sha256) at creation and only store the hash.

You see the full key once, at creation time, in the modal that generates it. Copy it then; we cannot retrieve it later.

Generate a key

In the chat web at https://<your-slug>.ownllm.app:

  1. Go to Profile → API keys.
  2. Click Generate a key.
  3. Pick a name (a short label so you remember which tool uses it), scopes (which models the key can use), an optional monthly budget in cents, and an optional expiration.
  4. Click Generate. Copy the key.

Use the key

curl https://acme-prod.ownllm.app/v1/chat/completions \
  -H "Authorization: Bearer sk-ownllm-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3.3-70b",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

The Authorization: Bearer <key> header is the only auth shape we accept. We do not look at ?api_key= query params or X-API-Key headers.

Scopes

A scope is a model id (llama-3.3-70b) the key is allowed to call. By default, a new key is scoped to every model the tenant has installed. Tighten this at creation if you want to restrict a key to, say, only the code model:

scopes: ["model:qwen2.5-coder:32b"]

The scope check happens before the request is forwarded. A key without model:<id> (or the wildcard model:*) gets a 403 with scope_required.

Budgets

A monthly budget caps how many tokens a key can spend in a calendar month. Budgets are tracked in cents internally — we use a flat internal cost-per-token table per model so a single budget applies across all models the key can call.

When a request would push the key over budget, we return:

{
  "error": {
    "type": "rate_limit_exceeded",
    "code": "budget_exceeded",
    "message": "Monthly budget of 5000c exceeded. Bump the budget in Profile → API keys."
  }
}

See Rate limits for the difference between budgets and per-second rate limits.

Expiration

You can set an expiration when generating a key (90 days, 6 months, 1 year, custom). Expired keys return 401 with key_expired. Generate a fresh one — you don't need to remove the old one explicitly, but doing so keeps the key list tidy.

Rotation

To rotate:

  1. Generate a new key with the same scopes and budget.
  2. Update the client to use the new key.
  3. Revoke the old key from Profile → API keys.

The site does not support an "in-place" rotation that keeps the same key string — that would defeat the purpose.

Revocation

A key is revoked the moment you click Revoke in the UI:

  • The hash is wiped on our side.
  • In-flight requests using the key are not cancelled but they return their last byte and then can't issue follow-ups.

When a user is deactivated (manual, or SCIM deprovision in Enterprise), all their keys are revoked automatically.

Per-key audit

Every request the key makes is recorded in audit logs at Admin → Audit (the admin web). The recorded fields are: timestamp, user, key prefix (last 6 chars), model, tokens in/out, duration, via (api). Prompt and completion content are not stored — this is metadata only.

On this page