---
sidebar_position: 8
title: API
description: The Lium platform REST API — live OpenAPI 3.1 schema, the single source of truth for every endpoint, payload and response.
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lium.io/llms.txt
> Use this file to discover all available pages before exploring further.

# API

The Lium platform API publishes a live [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) schema. Point any code-generator, agent runtime, or HTTP client at it to discover every endpoint, payload, and response.

| Resource | URL |
|----------|-----|
| Raw JSON spec | [`https://lium.io/api/openapi.json`](https://lium.io/api/openapi.json) |
| Swagger UI | [`https://lium.io/documents`](https://lium.io/documents) |

The schema is generated directly from the FastAPI app at request time, so it always matches the live deployment.

## Fetch the spec

```bash
curl -sSL https://lium.io/api/openapi.json -o lium-openapi.json
```

```python
import httpx

spec = httpx.get("https://lium.io/api/openapi.json").json()
print(spec["info"]["title"], spec["info"]["version"])
```

## Use it from an AI agent

### Ground Claude with the spec

Pass the spec to Claude as cached context, then let the model reason about which endpoint to call. The `cache_control` block keeps you from re-billing input tokens on every request — the spec is large but stable.

```python
import json, httpx, anthropic

spec_text = httpx.get("https://lium.io/api/openapi.json").text
client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    system=[
        {"type": "text", "text": "You manage Lium GPU pods. Use the OpenAPI spec to choose the right endpoint."},
        {"type": "text", "text": f"<openapi>\n{spec_text}\n</openapi>", "cache_control": {"type": "ephemeral"}},
    ],
    messages=[{"role": "user", "content": "List my running pods."}],
)
print(resp.content[0].text)
```

For real tool calling (model invokes endpoints directly), walk `spec["paths"]` and emit one Anthropic [tool definition](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) per operation — `name = operationId`, `input_schema = requestBody.content["application/json"].schema`.

### MCP-aware agents

If your agent already speaks [MCP](./mcp), point it at the docs MCP endpoint for prose questions and at this OpenAPI URL for direct API calls. Both are stable and require no extra configuration.

### Code generation

Generate a typed client in any language with the standard OpenAPI tooling:

```bash
# TypeScript / fetch
npx openapi-typescript https://lium.io/api/openapi.json -o lium.d.ts

# Python (httpx)
pip install openapi-python-client
openapi-python-client generate --url https://lium.io/api/openapi.json
```

## Authentication

Authenticated endpoints accept your API key in the **`X-API-Key`** request header. Get a key from **Access → API Keys** ([lium.io/ssh-keys](https://lium.io/ssh-keys), then the **API Keys** tab). See the [Developer Quickstart](./quickstart) for a complete first-call walkthrough and [API Authentication](./authentication) for the full reference: key sources and precedence, how to verify a key, and the error envelope.

```bash
export LIUM_API_KEY=your_api_key_here
curl https://lium.io/api/pods -H "X-API-Key: $LIUM_API_KEY"
```

The spec's `components.securitySchemes` declares `JwtAccessBearer` (`type: http`, `scheme: bearer`) — the session token the lium.io web app uses — and `APIKeyHeader` (`type: apiKey`, `in: header`, `name: X-API-Key`). Operations that accept an API key list both, so a generated client's security handler sends the key for you; what the key then reaches depends on its [scopes](/pod-users/api-keys#scopes). A key created without choosing holds `read`, `rent` and `manage`: `read` covers the `GET`s on pods, templates, volumes, SSH keys, backups, `GET /users/me` and the spend statement (`/billing`); `rent` starts pods and, like `manage`, allows writes to SSH keys, templates, volumes and backup schedules; `manage` controls pods the key did not start. Card checkout and saved cards (`/stripe/create-checkout-session`, `/stripe/payments/payment-methods`), credit transfers (behind a switch), the auto top-up read and the payment ledger (`GET /transactions`) need the `billing` scope, which no key gets by default. `billing` is held alone, so a billing key gets `403` on pods, templates, volumes and SSH keys; an agent that both rents and pays needs two keys (see [The `billing` scope](/pod-users/api-keys#the-billing-scope), which also covers the crypto top-ups that still take a default key). Operations that need a signed-in session list only `JwtAccessBearer` and answer `401` to a key alone, with the message `This route needs a signed-in session; it does not accept an API key`: creating and managing API keys (`/keys`; `GET /keys/scopes` is public and `GET /keys/{id}/refusals` takes the key it describes), payouts (`/payouts`), Stripe Connect onboarding (`POST /stripe/connect/*`), referrals, crypto wallets, `/feedbacks`, volume cost and usage reports (`/volumes/costs`, `/volumes/cost-summary`, `/volumes/{id}/current-stats`), creating and administering workspaces, and most account settings under `/users/me` (profile, notifications, billing profile, changing auto top-up, low-balance threshold). Public operations (`GET /executors`, `GET /templates`, which answer without a key) carry an empty security entry (`{}`) beside the two schemes, so a generated client treats them as optionally authenticated.

Missing or unknown keys are answered with `401` and the platform's standard error envelope:

```json
{"success": false, "error": "HTTP error", "message": "API key not found", "status_code": 401, "error_id": null, "timestamp": "…", "api_context": null}
```

## Reading a volume cost row

`GET /volumes/costs?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD` returns one row per volume per day (`group_by=date` sums them per day). The AWS fields are what AWS billed for the bucket; the two custom fields are what you are charged:

| field | meaning |
|---|---|
| `storage_cost`, `egress_cost`, `requests_cost`, `other_costs`, `total_cost` | AWS's own line items for the bucket that day, in USD |
| `custom_storage_cost` | stored bytes re-priced at Lium's storage rate ($0.00005 per GB-hour) |
| `chargeable_cost` | what was taken from your balance for that day: `custom_storage_cost` + `egress_cost` (egress at AWS cost, no free tier; older days may show storage only; rates on [Volumes → Billing](../pod-users/volumes.md#billing)) |

Known issue: until a routing fix (in review) is deployed, `GET /volumes/costs` and `GET /volumes/cost-summary` answer `422`: the `GET /volumes/{volume_id}` route catches both paths first.

`cost_type=egress` (or `storage`, `requests`, `other`, `custom_storage`, `chargeable`) narrows the totals to one component. See [Volumes → Billing](/pod-users/volumes#billing) for what counts as egress.

## Related

- [API Authentication](./authentication) — `X-API-Key`, key sources and precedence, verification, error envelope
- [Developer Quickstart](./quickstart) — first authenticated request in 5 minutes
- [SDK](./sdk) — typed Python client built on top of the same API
- [MCP endpoint](./mcp) — search and read the docs over JSON-RPC
- [llms.txt](./llms-txt) — bulk markdown bundle for LLM context
