# Exportar detalles de uso

> Guía de exportación de uso de LMU AI: inicia sesión para obtener un JWT, luego llama a /api/v1/usage con paginación, rango de fechas y filtrado por modelo, con ejemplos en tres lenguajes.

URL: https://docs.lmuai.com/es/docs/api/usage-export



Inicia sesión con tu cuenta y contraseña para obtener un JWT, luego usa ese JWT para llamar a `/api/v1/usage` y obtener **la tabla completa de detalles de uso de tokens, idéntica a la página `/usage` de la consola**.

Ideal para scripts de larga duración que automatizan la exportación, la conciliación y la carga en un BI empresarial — sin necesidad de descargar un CSV manualmente cada vez.

<Callout type="info">
  **¿Por qué no puedes obtenerlo directamente con una clave API `sk-...`?** La clave solo sirve para llamadas de negocio a través del gateway (/v1/messages, etc.) y no expone consultas de detalle a nivel de cuenta — esto es un diseño de seguridad deliberado: si una clave se filtra, no querrás que se vuelque toda tu factura. Los datos a nivel de cuenta deben usar autenticación de cuenta (JWT).
</Callout>

***

## 1. Resumen de la API [#1-resumen-de-la-api]

| Elemento                 | Valor                                                                                        |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| URL base                 | `https://api.lmuai.com`                                                                      |
| Autenticación            | `Authorization: Bearer <access_token>` (el JWT del inicio de sesión de la cuenta)            |
| Validez del access token | **86400 segundos (24 horas)**, renovable con refresh\_token                                  |
| Formato de respuesta     | JSON; `code: 0` significa éxito, en caso de fallo `code != 0` con un campo `message`         |
| Uso recomendado          | Exportación automatizada de facturación, carga en un BI empresarial, scripts de conciliación |

***

## 2. Paso 1 — Inicia sesión para obtener un JWT [#2-paso-1--inicia-sesión-para-obtener-un-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"
}
```

**Respuesta** (extracto):

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

Todas las solicitudes posteriores simplemente incluyen `Authorization: Bearer <access_token>`.

***

## 3. Paso 2 — Obtén los detalles de uso [#3-paso-2--obtén-los-detalles-de-uso]

### `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>
```

### Parámetros de consulta [#parámetros-de-consulta]

| Parámetro      | Tipo   | Requerido | Predeterminado | Descripción                                                                                                                                                       |
| -------------- | ------ | --------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `page`         | int    | No        | 1              | Número de página, empezando desde 1                                                                                                                               |
| `page_size`    | int    | No        | 20             | Filas por página, se recomienda 200 (los valores excesivos se truncan)                                                                                            |
| `start_date`   | string | No        | —              | Fecha de inicio `YYYY-MM-DD`, interpretada según `timezone`                                                                                                       |
| `end_date`     | string | No        | —              | Fecha final (incluye ese día)                                                                                                                                     |
| `timezone`     | string | No        | UTC            | Zona horaria para interpretar los parámetros de fecha, p. ej. `Asia/Shanghai`; **muy recomendable**, de lo contrario los límites no coincidirán con tu hora local |
| `api_key_id`   | int    | No        | —              | Ver los detalles de una sola clave (debe pertenecer al usuario actual)                                                                                            |
| `model`        | string | No        | —              | Filtrar por ID de modelo (p. ej. `claude-sonnet-5`)                                                                                                               |
| `stream`       | bool   | No        | —              | `true` solo para streaming, `false` solo para no streaming                                                                                                        |
| `billing_type` | int    | No        | —              | Enum de tipo de facturación (ver más abajo)                                                                                                                       |
| `sort_by`      | string | No        | `created_at`   | Campo de ordenamiento                                                                                                                                             |
| `sort_order`   | string | No        | `desc`         | `asc` o `desc`                                                                                                                                                    |

### Estructura de la respuesta [#estructura-de-la-respuesta]

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

<Callout type="warn">
  **Los campos de paginación están en el nivel superior de `data`, no en el envoltorio exterior**. El arreglo de detalles está en `data.items` — `data` en sí no es un arreglo.
</Callout>

### Mapeo de campos de un solo registro [#mapeo-de-campos-de-un-solo-registro]

Estos corresponden exactamente a cada columna de la página `/usage` de la consola:

| Columna de la consola          | Campo JSON                                                                 | Tipo           | Descripción                                                                     |
| ------------------------------ | -------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------- |
| **Modelo**                     | `model`                                                                    | string         | El ID del modelo realmente facturado                                            |
| **Esfuerzo de razonamiento**   | `reasoning_effort`                                                         | string \| null | `low` / `medium` / `high` / `xhigh` / `max`                                     |
| **Endpoint de entrada**        | `inbound_endpoint`                                                         | string \| null | La ruta que llamó el cliente, p. ej. `/v1/messages`                             |
| **Endpoint upstream**          | `upstream_endpoint`                                                        | string \| null | La ruta upstream normalizada                                                    |
| **Tipo de solicitud**          | `request_type`                                                             | string         | `chat` / `stream` / `responses` / `messages`, etc.                              |
| **Streaming**                  | `stream`                                                                   | bool           | Si fue streaming                                                                |
| **Modo de facturación**        | `billing_mode`                                                             | string         | `token` / `per_request` / `image` / `subscription`                              |
| **Tipo de facturación**        | `billing_type`                                                             | int            | Enum de tipo de facturación                                                     |
| **Multiplicador**              | `rate_multiplier`                                                          | float          | El multiplicador del canal aplicado a esta solicitud                            |
| **Tokens de entrada**          | `input_tokens`                                                             | int            |                                                                                 |
| **Tokens de salida**           | `output_tokens`                                                            | int            |                                                                                 |
| **Creación de caché**          | `cache_creation_tokens`                                                    | int            | Todas las escrituras de caché                                                   |
| **Creación de caché 5m**       | `cache_creation_5m_tokens`                                                 | int            | Nivel efímero de 5 minutos de Anthropic                                         |
| **Creación de caché 1h**       | `cache_creation_1h_tokens`                                                 | int            | Nivel de 1 hora de Anthropic                                                    |
| **Lectura de caché**           | `cache_read_tokens`                                                        | int            |                                                                                 |
| **Costo de entrada**           | `input_cost`                                                               | float          | USD                                                                             |
| **Costo de salida**            | `output_cost`                                                              | float          | USD                                                                             |
| **Costo de creación de caché** | `cache_creation_cost`                                                      | float          | USD                                                                             |
| **Costo de lectura de caché**  | `cache_read_cost`                                                          | float          | USD                                                                             |
| **Total a precio de lista**    | `total_cost`                                                               | float          | USD, antes del multiplicador                                                    |
| **Cargo real**                 | `actual_cost`                                                              | float          | **= total\_cost × rate\_multiplier**, lo que realmente se descuenta de tu saldo |
| **Latencia del primer token**  | `first_token_ms`                                                           | int \| null    | TTFT, válido para streaming                                                     |
| **Duración total**             | `duration_ms`                                                              | int \| null    |                                                                                 |
| **Hora**                       | `created_at`                                                               | ISO8601        | Hora de escritura del lado del servidor                                         |
| **ID de solicitud**            | `request_id`                                                               | string         | Para resolución de problemas y trazabilidad                                     |
| **Generación de imágenes**     | `image_count` / `image_size` / `image_output_tokens` / `image_output_cost` | —              | Se completa solo para solicitudes multimodales                                  |
| **API Key**                    | `api_key_id` + objeto anidado `api_key`                                    | int + obj      |                                                                                 |
| **Grupo**                      | `group_id` + objeto anidado `group`                                        | int + obj      |                                                                                 |

<Callout type="warn">
  Cada registro lleva objetos anidados `user` / `api_key` / `group` (unos +1KB por fila). **El objeto `api_key` contiene el texto completo de la clave — asegúrate de redactarlo en los logs / capturas de pantalla / CSVs de tus scripts**; para exportaciones masivas, si no necesitas los objetos anidados, usa `jq` o mapeo de campos en el cliente para conservar solo los campos que necesitas.
</Callout>

### Enum de tipo de facturación (billing\_type) [#enum-de-tipo-de-facturación-billing_type]

| Valor | Significado                                              |
| ----- | -------------------------------------------------------- |
| `0`   | Facturado por token (la gran mayoría de las solicitudes) |
| `1`   | Descontado de la cuota de una suscripción                |

El campo `billing_mode` es la versión en cadena de texto, que describe el modelo de precios específico (`token` / `per_request` / `image` / `subscription`, etc.); es más legible y se recomienda para la categorización.

***

## 4. Renovación del token [#4-renovación-del-token]

El access token expira después de 24 horas. Hay dos formas de renovarlo:

### Renovar con refresh\_token (recomendado para scripts) [#renovar-con-refresh_token-recomendado-para-scripts]

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

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

Devuelve un nuevo `access_token` (y normalmente también rota el `refresh_token`). El refresh token es válido durante 30 días.

### Iniciar sesión de nuevo [#iniciar-sesión-de-nuevo]

Rudimentario pero simple, adecuado para scripts de un solo uso: llama a `/auth/login` de nuevo antes de cada ejecución.

***

## 5. Ejemplos de código [#5-ejemplos-de-código]

### 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.com"

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.com"
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.com'
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. Notas prácticas [#6-notas-prácticas]

1. **Agrega un retraso entre cambios de página masivos** — al paginar N páginas seguidas, se recomienda `sleep(0.1~0.3s)` para evitar activar los límites de tasa.
2. **No obtengas un rango de tiempo demasiado largo de una vez** — abarcar varios meses y millones de filas es lento. Segmentar por mes es más estable.
3. **Se recomienda un `page_size` de 200** — el servidor trunca los valores excesivos.
4. **Ahorra ancho de banda** — los objetos anidados `user` / `api_key` / `group` son grandes; si solo quieres las filas de detalle, fíltralas con `jq` en el cliente.
5. **Errores comunes**:
   * `401` JWT expirado → renueva con `refresh_token` o inicia sesión de nuevo
   * `403` Acceso no autorizado (p. ej. consultar la clave de otra persona vía `api_key_id`) → verifica que la clave pertenezca al usuario actual
   * `400` Parámetro incorrecto (p. ej. formato de `start_date`) → verifica que `YYYY-MM-DD` + `timezone` sean válidos

***

## 7. Recomendaciones de seguridad (lectura obligatoria) [#7-recomendaciones-de-seguridad-lectura-obligatoria]

<Callout type="warn">
  **Tu contraseña de cuenta + JWT equivalen a acceso completo a la cuenta.** Antes de scriptear una exportación, asegúrate de:
</Callout>

| Elemento                | Recomendación                                                                                                                                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gestión de credenciales | **Nunca** comprometas la contraseña / access\_token / refresh\_token en git o en los logs de CI; usa variables de entorno, un gestor de secretos o `.env` (con `.gitignore`)                         |
| Seguridad de la cuenta  | Se recomienda encarecidamente habilitar la **autenticación de dos factores TOTP** (habilítala en la configuración de la cuenta). Esta API admite la verificación TOTP durante el inicio de sesión    |
| Rotación periódica      | Cambia tu contraseña de cuenta regularmente; rota el refresh\_token inmediatamente después de que expire                                                                                             |
| Monitoreo de anomalías  | Vigila el "último inicio de sesión / IP" de la cuenta; cambia tu contraseña de inmediato si detectas un dispositivo desconocido                                                                      |
| Redacción de campos     | El `api_key.key` anidado (texto completo sk-...), `user.email` y `refresh_token` en la respuesta son todos campos sensibles — **redáctalos antes de la salida / logs / CSV**                         |
| Privilegio mínimo       | Si puedes separarlas, **usa una subcuenta dedicada para el script de exportación** (si tu organización tiene una estructura multicuenta) para evitar la exposición prolongada de la cuenta principal |
| Reintentos ante errores | No reintentes indefinidamente ante un fallo de inicio de sesión — es fácil que active los controles de riesgo; se recomienda backoff exponencial                                                     |

***

## 8. Referencia rápida de campos [#8-referencia-rápida-de-campos]

Útil para el mapeo de columnas de CSV:

| Etiqueta                     | Campo                         | Unidad       |
| ---------------------------- | ----------------------------- | ------------ |
| Modelo                       | `model`                       | —            |
| Esfuerzo de razonamiento     | `reasoning_effort`            | —            |
| Endpoint de entrada          | `inbound_endpoint`            | ruta         |
| Endpoint upstream            | `upstream_endpoint`           | ruta         |
| Tipo de solicitud            | `request_type`                | enum         |
| Streaming                    | `stream`                      | bool         |
| Modo de facturación          | `billing_mode`                | enum         |
| Tipo de facturación          | `billing_type`                | int          |
| Multiplicador                | `rate_multiplier`             | float        |
| Tokens de entrada            | `input_tokens`                | int          |
| Tokens de salida             | `output_tokens`               | int          |
| Tokens de lectura de caché   | `cache_read_tokens`           | int          |
| Tokens de escritura de caché | `cache_creation_tokens`       | int          |
| Escritura de caché 5m        | `cache_creation_5m_tokens`    | int          |
| Escritura de caché 1h        | `cache_creation_1h_tokens`    | int          |
| Costo de entrada             | `input_cost`                  | USD          |
| Costo de salida              | `output_cost`                 | USD          |
| Costo de creación de caché   | `cache_creation_cost`         | USD          |
| Costo de lectura de caché    | `cache_read_cost`             | USD          |
| Total a precio de lista      | `total_cost`                  | USD          |
| Cargo real                   | `actual_cost`                 | USD          |
| Primer token                 | `first_token_ms`              | ms           |
| Duración total               | `duration_ms`                 | ms           |
| Hora                         | `created_at`                  | ISO8601      |
| ID de solicitud              | `request_id`                  | string       |
| Grupo                        | `group_id` / `group.name`     | int / string |
| API Key                      | `api_key_id` / `api_key.name` | int / string |
