# Export Usage Details

> LMU AI Usage export guide: log in for a JWT, then call /api/v1/usage with pagination, date range and model filtering, with examples in three languages.

URL: https://docs.lmuai.ai/docs/api/usage-export



Log in with your account and password to get a JWT, then use that JWT to call `/api/v1/usage` and pull **the full token usage details table, identical to the console `/usage` page**.

Ideal for long-running scripts that automate export, reconciliation, and loading into enterprise BI — no need to manually download a CSV each time.

<Callout type="info">
  **Why can't you pull it directly with an `sk-...` API key?** The key is only for gateway business calls (/v1/messages, etc.) and does not expose account-level detail queries — this is a deliberate security design: if a key leaks, you don't want your entire bill dumped. Account-level data must use account authentication (JWT).
</Callout>

***

## 1. API overview [#1-api-overview]

| Item                  | Value                                                                        |
| --------------------- | ---------------------------------------------------------------------------- |
| Base URL              | `https://api.lmuai.ai`                                                       |
| Authentication        | `Authorization: Bearer <access_token>` (the JWT from account login)          |
| Access token validity | **86400 seconds (24 hours)**, renewable with refresh\_token                  |
| Response format       | JSON; `code: 0` means success, on failure `code != 0` with a `message` field |
| Recommended use       | Automated billing export, loading into enterprise BI, reconciliation scripts |

***

## 2. Step 1 — Log in to get a JWT [#2-step-1--log-in-to-get-a-jwt]

### `POST /api/v1/auth/login` [#post-apiv1authlogin]

```http
POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json

{
  "email": "your@email.com",
  "password": "your-password"
}
```

**Response** (excerpt):

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "access_token": "eyJhbGc...",
    "refresh_token": "rt_756a220ffa...",
    "expires_in": 86400,
    "token_type": "Bearer",
    "user": { "id": 3, "email": "...", "balance": 1575.13 }
  }
}
```

All subsequent requests just include `Authorization: Bearer <access_token>`.

***

## 3. Step 2 — Pull the usage details [#3-step-2--pull-the-usage-details]

### `GET /api/v1/usage` [#get-apiv1usage]

```http
GET /api/v1/usage?page=1&page_size=200&start_date=2026-06-01&end_date=2026-06-23&timezone=Asia/Shanghai
Authorization: Bearer <access_token>
```

### Query parameters [#query-parameters]

| Parameter      | Type   | Required | Default      | Description                                                                                                                                    |
| -------------- | ------ | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `page`         | int    | No       | 1            | Page number, starting from 1                                                                                                                   |
| `page_size`    | int    | No       | 20           | Rows per page, 200 recommended (oversized values are truncated)                                                                                |
| `start_date`   | string | No       | —            | Start date `YYYY-MM-DD`, parsed by `timezone`                                                                                                  |
| `end_date`     | string | No       | —            | End date (inclusive of that day)                                                                                                               |
| `timezone`     | string | No       | UTC          | Timezone for parsing the date parameters, e.g. `Asia/Shanghai`; **strongly recommended**, otherwise the boundaries won't match your local time |
| `api_key_id`   | int    | No       | —            | View only one key's details (must belong to the current user)                                                                                  |
| `model`        | string | No       | —            | Filter by model ID (e.g. `claude-sonnet-5`)                                                                                                    |
| `stream`       | bool   | No       | —            | `true` for streaming only, `false` for non-streaming only                                                                                      |
| `billing_type` | int    | No       | —            | Billing type enum (see below)                                                                                                                  |
| `sort_by`      | string | No       | `created_at` | Sort field                                                                                                                                     |
| `sort_order`   | string | No       | `desc`       | `asc` or `desc`                                                                                                                                |

### Response structure [#response-structure]

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "items": [ { /* UsageLog */ }, ... ],
    "total": 30423,
    "page": 1,
    "page_size": 200,
    "pages": 153
  }
}
```

<Callout type="warn">
  **The pagination fields are at the top level of `data`, not in the outer envelope**. The details array is at `data.items` — `data` is not itself an array.
</Callout>

### Field mapping for a single record [#field-mapping-for-a-single-record]

These correspond exactly to each column of the console `/usage` page:

| Console column          | JSON field                                                                 | Type           | Description                                                                       |
| ----------------------- | -------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------- |
| **Model**               | `model`                                                                    | string         | The model ID actually billed                                                      |
| **Reasoning effort**    | `reasoning_effort`                                                         | string \| null | `low` / `medium` / `high` / `xhigh` / `max`                                       |
| **Inbound endpoint**    | `inbound_endpoint`                                                         | string \| null | The path the client called, e.g. `/v1/messages`                                   |
| **Upstream endpoint**   | `upstream_endpoint`                                                        | string \| null | The normalized upstream path                                                      |
| **Request type**        | `request_type`                                                             | string         | `chat` / `stream` / `responses` / `messages`, etc.                                |
| **Streaming**           | `stream`                                                                   | bool           | Whether it was streaming                                                          |
| **Billing mode**        | `billing_mode`                                                             | string         | `token` / `per_request` / `image` / `subscription`                                |
| **Billing type**        | `billing_type`                                                             | int            | Billing type enum                                                                 |
| **Multiplier**          | `rate_multiplier`                                                          | float          | The channel multiplier applied to this request                                    |
| **Input tokens**        | `input_tokens`                                                             | int            |                                                                                   |
| **Output tokens**       | `output_tokens`                                                            | int            |                                                                                   |
| **Cache creation**      | `cache_creation_tokens`                                                    | int            | All cache writes                                                                  |
| **Cache creation 5m**   | `cache_creation_5m_tokens`                                                 | int            | Anthropic ephemeral 5-minute tier                                                 |
| **Cache creation 1h**   | `cache_creation_1h_tokens`                                                 | int            | Anthropic 1-hour tier                                                             |
| **Cache read**          | `cache_read_tokens`                                                        | int            |                                                                                   |
| **Input cost**          | `input_cost`                                                               | float          | USD                                                                               |
| **Output cost**         | `output_cost`                                                              | float          | USD                                                                               |
| **Cache creation cost** | `cache_creation_cost`                                                      | float          | USD                                                                               |
| **Cache read cost**     | `cache_read_cost`                                                          | float          | USD                                                                               |
| **List-price total**    | `total_cost`                                                               | float          | USD, before the multiplier                                                        |
| **Actual charge**       | `actual_cost`                                                              | float          | **= total\_cost × rate\_multiplier**, what is actually deducted from your balance |
| **First-token latency** | `first_token_ms`                                                           | int \| null    | TTFT, valid for streaming                                                         |
| **Total duration**      | `duration_ms`                                                              | int \| null    |                                                                                   |
| **Time**                | `created_at`                                                               | ISO8601        | Server-side write time                                                            |
| **Request ID**          | `request_id`                                                               | string         | For troubleshooting and tracing                                                   |
| **Image generation**    | `image_count` / `image_size` / `image_output_tokens` / `image_output_cost` | —              | Populated only for multimodal requests                                            |
| **API Key**             | `api_key_id` + nested `api_key` object                                     | int + obj      |                                                                                   |
| **Group**               | `group_id` + nested `group` object                                         | int + obj      |                                                                                   |

<Callout type="warn">
  Each record carries nested `user` / `api_key` / `group` objects (about +1KB per row). **The `api_key` object contains the full key text — be sure to redact it in script logs / screenshots / CSVs**; for bulk export, if you don't need the nested objects, use `jq` or field mapping on the client to keep only the fields you need.
</Callout>

### Billing type enum (billing\_type) [#billing-type-enum-billing_type]

| Value | Meaning                                         |
| ----- | ----------------------------------------------- |
| `0`   | Billed by token (the vast majority of requests) |
| `1`   | Deducted from a subscription's quota            |

The `billing_mode` field is the string version, describing the specific pricing model (`token` / `per_request` / `image` / `subscription`, etc.); it is more readable and recommended for categorization.

***

## 4. Token renewal [#4-token-renewal]

The access token expires after 24 hours. There are two ways to renew it:

### Renew with refresh\_token (recommended for scripts) [#renew-with-refresh_token-recommended-for-scripts]

```http
POST /api/v1/auth/refresh
Content-Type: application/json

{
  "refresh_token": "rt_756a220ffa..."
}
```

Returns a new `access_token` (and usually rotates the `refresh_token` too). The refresh token is valid for 30 days.

### Log in again [#log-in-again]

Crude but simple, good for one-off scripts: call `/auth/login` again before each run.

***

## 5. Code examples [#5-code-examples]

### Bash + curl + jq [#bash--curl--jq]

```bash
#!/usr/bin/env bash
set -e

EMAIL="${EMAIL:?set EMAIL env}"
PASSWORD="${PASSWORD:?set PASSWORD env}"
BASE="https://api.lmuai.ai"

JWT=$(curl -s -X POST "$BASE/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" \
  | jq -r '.data.access_token')

[ -n "$JWT" ] && [ "$JWT" != "null" ] || { echo "login failed" >&2; exit 1; }

START="2026-06-01"; END="2026-06-30"; TZ="Asia/Shanghai"; PAGE_SIZE=200

# Fetch page 1 to get total
META=$(curl -s "$BASE/api/v1/usage?page=1&page_size=$PAGE_SIZE&start_date=$START&end_date=$END&timezone=$TZ" \
  -H "Authorization: Bearer $JWT")
PAGES=$(echo "$META" | jq -r '.data.pages')

# CSV header
echo "created_at,model,request_type,input_tokens,output_tokens,cache_read_tokens,total_cost,actual_cost,duration_ms" > usage.csv

# Write page 1
echo "$META" | jq -r '.data.items[] | [.created_at, .model, .request_type, .input_tokens, .output_tokens, .cache_read_tokens, .total_cost, .actual_cost, .duration_ms] | @csv' >> usage.csv

# Fetch remaining pages
for ((p=2; p<=PAGES; p++)); do
  curl -s "$BASE/api/v1/usage?page=$p&page_size=$PAGE_SIZE&start_date=$START&end_date=$END&timezone=$TZ" \
    -H "Authorization: Bearer $JWT" \
    | jq -r '.data.items[] | [.created_at, .model, .request_type, .input_tokens, .output_tokens, .cache_read_tokens, .total_cost, .actual_cost, .duration_ms] | @csv' >> usage.csv
done

echo "saved $(wc -l < usage.csv) lines to usage.csv"
```

### Python [#python]

```python
import os, csv, requests

BASE = "https://api.lmuai.ai"
EMAIL = os.environ["EMAIL"]
PASSWORD = os.environ["PASSWORD"]

def login() -> str:
    r = requests.post(f"{BASE}/api/v1/auth/login",
                      json={"email": EMAIL, "password": PASSWORD},
                      timeout=15)
    r.raise_for_status()
    data = r.json()
    if data["code"] != 0:
        raise RuntimeError(data.get("message", "login failed"))
    return data["data"]["access_token"]

def fetch_usage(jwt: str, **params):
    """Yield each row from paginated /usage endpoint."""
    page = 1
    while True:
        r = requests.get(f"{BASE}/api/v1/usage",
                         headers={"Authorization": f"Bearer {jwt}"},
                         params={**params, "page": page, "page_size": 200},
                         timeout=30)
        r.raise_for_status()
        d = r.json()["data"]
        for row in d.get("items", []):
            yield row
        if page >= d.get("pages", 1):
            return
        page += 1

if __name__ == "__main__":
    jwt = login()
    cols = ["created_at","model","request_type","input_tokens","output_tokens",
            "cache_read_tokens","total_cost","actual_cost","duration_ms"]
    with open("usage.csv", "w", newline="") as f:
        w = csv.writer(f); w.writerow(cols)
        for row in fetch_usage(jwt, start_date="2026-06-01", end_date="2026-06-30",
                               timezone="Asia/Shanghai"):
            w.writerow([row.get(c) for c in cols])
    print("done")
```

### Node.js [#nodejs]

```js
import fs from 'node:fs'

const BASE = 'https://api.lmuai.ai'
const { EMAIL, PASSWORD } = process.env

async function login() {
  const r = await fetch(`${BASE}/api/v1/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
  })
  const j = await r.json()
  if (j.code !== 0) throw new Error(j.message || 'login failed')
  return j.data.access_token
}

async function* fetchUsage(jwt, params) {
  for (let page = 1; ; page++) {
    const q = new URLSearchParams({ ...params, page, page_size: '200' })
    const r = await fetch(`${BASE}/api/v1/usage?${q}`, {
      headers: { Authorization: `Bearer ${jwt}` },
    })
    const j = await r.json()
    for (const row of j.data.items) yield row
    if (page >= j.data.pages) return
  }
}

const jwt = await login()
const cols = ['created_at','model','request_type','input_tokens','output_tokens',
              'cache_read_tokens','total_cost','actual_cost','duration_ms']
const out = fs.createWriteStream('usage.csv')
out.write(cols.join(',') + '\n')
for await (const row of fetchUsage(jwt, {
  start_date: '2026-06-01', end_date: '2026-06-30', timezone: 'Asia/Shanghai',
})) {
  out.write(cols.map(c => JSON.stringify(row[c] ?? '')).join(',') + '\n')
}
console.log('done')
```

***

## 6. Practical notes [#6-practical-notes]

1. **Add a delay between bulk page turns** — when paging through N pages in a row, `sleep(0.1~0.3s)` is recommended to avoid triggering rate limits.
2. **Don't pull too long a time range at once** — spanning several months and millions of rows is slow. Slicing by month is more stable.
3. **`page_size` of 200 recommended** — the server truncates oversized values.
4. **Save bandwidth** — the nested `user` / `api_key` / `group` objects are large; if you only want the detail rows, filter with `jq` on the client.
5. **Common errors**:
   * `401` JWT expired → renew with `refresh_token` or log in again
   * `403` Unauthorized access (e.g. querying someone else's key via `api_key_id`) → check that the key belongs to the current user
   * `400` Bad parameter (e.g. `start_date` format) → check that `YYYY-MM-DD` + `timezone` are valid

***

## 7. Security recommendations (must read) [#7-security-recommendations-must-read]

<Callout type="warn">
  **Your account password + JWT are equivalent to full account access.** Before scripting an export, make sure:
</Callout>

| Item                  | Recommendation                                                                                                                                                                       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Credential management | **Never** commit password / access\_token / refresh\_token to git or CI logs; use environment variables, a secrets manager, or `.env` (with `.gitignore`)                            |
| Account security      | Strongly recommend enabling **TOTP two-factor authentication** (enable it in account settings). This API supports TOTP verification during login                                     |
| Regular rotation      | Change your account password regularly; rotate the refresh\_token immediately after it expires                                                                                       |
| Anomaly monitoring    | Watch the account's "last login time / IP"; change your password immediately if you spot an unfamiliar device                                                                        |
| Field redaction       | The nested `api_key.key` (full sk-... text), `user.email`, and `refresh_token` in the response are all sensitive fields — **redact them before output / logs / CSV**                 |
| Least privilege       | If you can separate them, **use a dedicated sub-account for the export script** (if your organization has a multi-account structure) to avoid long-term exposure of the main account |
| Error retries         | Don't retry indefinitely on login failure — it easily triggers risk controls; exponential backoff is recommended                                                                     |

***

## 8. Field quick reference [#8-field-quick-reference]

Handy for CSV column mapping:

| Label               | Field                         | Unit         |
| ------------------- | ----------------------------- | ------------ |
| Model               | `model`                       | —            |
| Reasoning effort    | `reasoning_effort`            | —            |
| Inbound endpoint    | `inbound_endpoint`            | path         |
| Upstream endpoint   | `upstream_endpoint`           | path         |
| Request type        | `request_type`                | enum         |
| Streaming           | `stream`                      | bool         |
| Billing mode        | `billing_mode`                | enum         |
| Billing type        | `billing_type`                | int          |
| Multiplier          | `rate_multiplier`             | float        |
| Input tokens        | `input_tokens`                | int          |
| Output tokens       | `output_tokens`               | int          |
| Cache read tokens   | `cache_read_tokens`           | int          |
| Cache write tokens  | `cache_creation_tokens`       | int          |
| Cache write 5m      | `cache_creation_5m_tokens`    | int          |
| Cache write 1h      | `cache_creation_1h_tokens`    | int          |
| Input cost          | `input_cost`                  | USD          |
| Output cost         | `output_cost`                 | USD          |
| Cache creation cost | `cache_creation_cost`         | USD          |
| Cache read cost     | `cache_read_cost`             | USD          |
| List-price total    | `total_cost`                  | USD          |
| Actual charge       | `actual_cost`                 | USD          |
| First token         | `first_token_ms`              | ms           |
| Total duration      | `duration_ms`                 | ms           |
| Time                | `created_at`                  | ISO8601      |
| Request ID          | `request_id`                  | string       |
| Group               | `group_id` / `group.name`     | int / string |
| API Key             | `api_key_id` / `api_key.name` | int / string |
