Open API

Nutzungsdetails exportieren

LMU-AI-Nutzungsexport-Anleitung: Anmelden für ein JWT, dann /api/v1/usage mit Paginierung, Datumsbereich und Modellfilterung aufrufen, mit Beispielen in drei Sprachen.

Melde dich mit Konto und Passwort an, um ein JWT zu erhalten, und verwende dann dieses JWT, um /api/v1/usage aufzurufen und die vollständige Tabelle der Token-Nutzungsdetails abzurufen, identisch mit der Konsolenseite /usage.

Ideal für langlaufende Skripte, die Export, Abstimmung und das Laden in Enterprise-BI automatisieren — kein manuelles Herunterladen einer CSV-Datei jedes Mal nötig.

Warum kannst du sie nicht direkt mit einem sk-...-API-Schlüssel abrufen? Der Schlüssel ist nur für Gateway-Geschäftsaufrufe (/v1/messages usw.) gedacht und legt keine Detailabfragen auf Kontoebene offen — das ist ein bewusstes Sicherheitsdesign: Wenn ein Schlüssel durchsickert, willst du nicht, dass deine gesamte Abrechnung ausgeschüttet wird. Daten auf Kontoebene müssen die Kontoauthentifizierung (JWT) verwenden.


1. API-Überblick

PunktWert
Basis-URLhttps://api.lmuai.com
AuthentifizierungAuthorization: Bearer <access_token> (das JWT aus dem Konto-Login)
Gültigkeit des Access-Tokens86400 Sekunden (24 Stunden), erneuerbar mit refresh_token
AntwortformatJSON; code: 0 bedeutet Erfolg, bei Fehler code != 0 mit einem message-Feld
Empfohlene VerwendungAutomatisierter Abrechnungsexport, Laden in Enterprise-BI, Abstimmungsskripte

2. Schritt 1 — Anmelden, um ein JWT zu erhalten

POST /api/v1/auth/login

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

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

Antwort (Auszug):

{
  "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 }
  }
}

Alle nachfolgenden Anfragen enthalten einfach Authorization: Bearer <access_token>.


3. Schritt 2 — Nutzungsdetails abrufen

GET /api/v1/usage

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>

Abfrageparameter

ParameterTypErforderlichStandardBeschreibung
pageintNein1Seitennummer, beginnend bei 1
page_sizeintNein20Zeilen pro Seite, 200 empfohlen (übergroße Werte werden gekürzt)
start_datestringNeinStartdatum YYYY-MM-DD, geparst nach timezone
end_datestringNeinEnddatum (einschließlich dieses Tages)
timezonestringNeinUTCZeitzone zum Parsen der Datumsparameter, z. B. Asia/Shanghai; dringend empfohlen, andernfalls stimmen die Grenzen nicht mit deiner lokalen Zeit überein
api_key_idintNeinNur die Details eines Schlüssels anzeigen (muss dem aktuellen Benutzer gehören)
modelstringNeinNach Modell-ID filtern (z. B. claude-sonnet-5)
streamboolNeintrue nur für Streaming, false nur für Nicht-Streaming
billing_typeintNeinAbrechnungstyp-Enum (siehe unten)
sort_bystringNeincreated_atSortierfeld
sort_orderstringNeindescasc oder desc

Antwortstruktur

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

Die Paginierungsfelder befinden sich auf der obersten Ebene von data, nicht in der äußeren Hülle. Das Detail-Array befindet sich unter data.itemsdata ist selbst kein Array.

Feldzuordnung für einen einzelnen Datensatz

Diese entsprechen exakt jeder Spalte der Konsolenseite /usage:

KonsolenspalteJSON-FeldTypBeschreibung
ModellmodelstringDie tatsächlich abgerechnete Modell-ID
Reasoning-Aufwandreasoning_effortstring | nulllow / medium / high / xhigh / max
Eingangsendpunktinbound_endpointstring | nullDer Pfad, den der Client aufgerufen hat, z. B. /v1/messages
Upstream-Endpunktupstream_endpointstring | nullDer normalisierte Upstream-Pfad
Anfragetyprequest_typestringchat / stream / responses / messages usw.
StreamingstreamboolOb es Streaming war
Abrechnungsmodusbilling_modestringtoken / per_request / image / subscription
Abrechnungstypbilling_typeintAbrechnungstyp-Enum
Multiplikatorrate_multiplierfloatDer auf diese Anfrage angewendete Kanal-Multiplikator
Eingabe-Tokensinput_tokensint
Ausgabe-Tokensoutput_tokensint
Cache-Erstellungcache_creation_tokensintAlle Cache-Schreibvorgänge
Cache-Erstellung 5mcache_creation_5m_tokensintAnthropic ephemere 5-Minuten-Stufe
Cache-Erstellung 1hcache_creation_1h_tokensintAnthropic 1-Stunden-Stufe
Cache-Lesevorgangcache_read_tokensint
Eingabekosteninput_costfloatUSD
Ausgabekostenoutput_costfloatUSD
Cache-Erstellungskostencache_creation_costfloatUSD
Cache-Lesekostencache_read_costfloatUSD
Listenpreis gesamttotal_costfloatUSD, vor dem Multiplikator
Tatsächliche Belastungactual_costfloat= total_cost × rate_multiplier, was tatsächlich von deinem Guthaben abgezogen wird
First-Token-Latenzfirst_token_msint | nullTTFT, gültig für Streaming
Gesamtdauerduration_msint | null
Zeitcreated_atISO8601Serverseitige Schreibzeit
Anfrage-IDrequest_idstringZur Fehlersuche und Nachverfolgung
Bildgenerierungimage_count / image_size / image_output_tokens / image_output_costNur bei multimodalen Anfragen befüllt
API-Schlüsselapi_key_id + verschachteltes api_key-Objektint + obj
Gruppegroup_id + verschachteltes group-Objektint + obj

Jeder Datensatz trägt verschachtelte user- / api_key- / group-Objekte (etwa +1 KB pro Zeile). Das api_key-Objekt enthält den vollständigen Schlüsseltext — achte darauf, ihn in Skript-Logs / Screenshots / CSVs zu schwärzen; für den Massenexport verwende, wenn du die verschachtelten Objekte nicht brauchst, jq oder eine Feldzuordnung auf dem Client, um nur die benötigten Felder zu behalten.

Abrechnungstyp-Enum (billing_type)

WertBedeutung
0Nach Token abgerechnet (die überwiegende Mehrheit der Anfragen)
1Von einem Abonnement-Kontingent abgezogen

Das billing_mode-Feld ist die String-Version, die das spezifische Preismodell beschreibt (token / per_request / image / subscription usw.); es ist besser lesbar und wird für die Kategorisierung empfohlen.


4. Token-Erneuerung

Das Access-Token läuft nach 24 Stunden ab. Es gibt zwei Möglichkeiten, es zu erneuern:

Mit refresh_token erneuern (für Skripte empfohlen)

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

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

Gibt ein neues access_token zurück (und rotiert normalerweise auch das refresh_token). Das Refresh-Token ist 30 Tage gültig.

Erneut anmelden

Grob, aber einfach, gut für einmalige Skripte: Rufe vor jedem Lauf erneut /auth/login auf.


5. Codebeispiele

Bash + curl + jq

#!/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

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

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. Praktische Hinweise

  1. Füge eine Verzögerung zwischen dem Blättern durch mehrere Seiten ein — wenn du N Seiten hintereinander durchblätterst, wird sleep(0.1~0.3s) empfohlen, um das Auslösen von Rate-Limits zu vermeiden.
  2. Ziehe nicht zu lange Zeiträume auf einmal — mehrere Monate und Millionen von Zeilen sind langsam. Ein Aufteilen nach Monat ist stabiler.
  3. page_size von 200 empfohlen — der Server kürzt übergroße Werte.
  4. Bandbreite sparen — die verschachtelten user- / api_key- / group-Objekte sind groß; wenn du nur die Detailzeilen willst, filtere mit jq auf dem Client.
  5. Häufige Fehler:
    • 401 JWT abgelaufen → mit refresh_token erneuern oder erneut anmelden
    • 403 Nicht autorisierter Zugriff (z. B. Abfrage des Schlüssels eines anderen über api_key_id) → prüfe, ob der Schlüssel dem aktuellen Benutzer gehört
    • 400 Fehlerhafter Parameter (z. B. start_date-Format) → prüfe, ob YYYY-MM-DD + timezone gültig sind

7. Sicherheitsempfehlungen (unbedingt lesen)

Dein Konto-Passwort + JWT entsprechen dem vollständigen Kontozugriff. Bevor du einen Export skriptest, stelle sicher, dass:

PunktEmpfehlung
Verwaltung von ZugangsdatenCommitte niemals password / access_token / refresh_token in Git oder CI-Logs; verwende Umgebungsvariablen, einen Secrets-Manager oder .env (mit .gitignore)
KontosicherheitEs wird dringend empfohlen, die TOTP-Zwei-Faktor-Authentifizierung zu aktivieren (in den Kontoeinstellungen aktivieren). Diese API unterstützt die TOTP-Verifizierung beim Login
Regelmäßige RotationÄndere dein Konto-Passwort regelmäßig; rotiere das refresh_token sofort nach dessen Ablauf
AnomalieüberwachungBeobachte die „letzte Login-Zeit / IP" des Kontos; ändere dein Passwort sofort, wenn du ein unbekanntes Gerät entdeckst
FeldschwärzungDer verschachtelte api_key.key (vollständiger sk-...-Text), user.email und refresh_token in der Antwort sind allesamt sensible Felder — schwärze sie vor der Ausgabe / in Logs / CSV
Least PrivilegeWenn du sie trennen kannst, verwende ein dediziertes Unterkonto für das Export-Skript (falls deine Organisation eine Mehrkonten-Struktur hat), um eine langfristige Exposition des Hauptkontos zu vermeiden
FehlerwiederholungenWiederhole bei einem Login-Fehler nicht endlos — das löst leicht Risikokontrollen aus; exponentielles Backoff wird empfohlen

8. Feld-Schnellreferenz

Praktisch für die CSV-Spaltenzuordnung:

LabelFeldEinheit
Modellmodel
Reasoning-Aufwandreasoning_effort
Eingangsendpunktinbound_endpointPfad
Upstream-Endpunktupstream_endpointPfad
Anfragetyprequest_typeenum
Streamingstreambool
Abrechnungsmodusbilling_modeenum
Abrechnungstypbilling_typeint
Multiplikatorrate_multiplierfloat
Eingabe-Tokensinput_tokensint
Ausgabe-Tokensoutput_tokensint
Cache-Lese-Tokenscache_read_tokensint
Cache-Schreib-Tokenscache_creation_tokensint
Cache-Schreiben 5mcache_creation_5m_tokensint
Cache-Schreiben 1hcache_creation_1h_tokensint
Eingabekosteninput_costUSD
Ausgabekostenoutput_costUSD
Cache-Erstellungskostencache_creation_costUSD
Cache-Lesekostencache_read_costUSD
Listenpreis gesamttotal_costUSD
Tatsächliche Belastungactual_costUSD
First Tokenfirst_token_msms
Gesamtdauerduration_msms
Zeitcreated_atISO8601
Anfrage-IDrequest_idstring
Gruppegroup_id / group.nameint / string
API-Schlüsselapi_key_id / api_key.nameint / string

Zuletzt aktualisiert:

Auf dieser Seite