Zum Hauptinhalt springen
Zurück zur Dokumentation

Lunidex Public API v1

Verbinde eine serverseitige Integration mit den Daten, die bereits mit einem Lunidex-Konto synchronisiert wurden. Erstelle einen Schlüssel und sende eine erste Anfrage. Danach folgen Karten, Sealed-Produkte, Pagination und sichere Schreibzugriffe.

Schlüssel nur für Server

Ein API-Schlüssel gehört niemals in Browsercode, eine mobile App, ein öffentliches Repository, eine URL, einen Screenshot oder ein gemeinsam genutztes Protokoll. Speichere ihn in einem serverseitigen Secret-Manager und sende ihn nur im Authorization-Header.

Schnellstart

Die Integration benötigt einen Schlüssel, ein serverseitig gespeichertes Secret und eine HTTPS-Anfrage an die API-Basis-URL.

  1. 1

    Schlüssel erstellen

    Öffne die API-Schlüsselkarte im Lunidex-Dashboard. Vergib einen Namen und wähle Lesezugriff oder Lese- und Schreibzugriff. Das vollständige Secret wird nur einmal angezeigt. Kopiere es, bevor du die Meldung schließt.

  2. 2

    Secret auf dem Server speichern

    Speichere den Schlüssel als LUNIDEX_API_KEY im Secret-Manager deines Hosting-Anbieters. Committe ihn nicht und sende ihn nicht an den Browser.

  3. 3

    Erste Anfrage senden

    Rufe GET /me von deinem Backend auf. Private Endpunkte benötigen den Header Authorization: Bearer gefolgt vom Schlüssel.

cURL · in einer vertrauenswürdigen Serverumgebung ausführen
curl --fail-with-body \
  -H "Authorization: Bearer $LUNIDEX_API_KEY" \
  https://lunidex.app/api/v1/me
Node.js · serverseitiges fetch
const API_BASE_URL = "https://lunidex.app/api/v1";
const apiKey = process.env.LUNIDEX_API_KEY;

if (!apiKey) throw new Error("Missing LUNIDEX_API_KEY");

const response = await fetch(API_BASE_URL + "/me", {
  headers: { Authorization: "Bearer " + apiKey },
});
const result = await response.json();

if (!response.ok) {
  throw new Error(result.error?.code ?? "REQUEST_FAILED");
}

console.log(result.data);

Authentifizierung und Berechtigungen

Sende den Schlüssel bei jeder privaten /api/v1-Anfrage im HTTP-Header Authorization. Die API liefert JSON in einem data-Umschlag; private Antworten verwenden Cache-Control: private, no-store.

Lesen

Erlaubt GET-Endpunkte. Wähle diese Berechtigung, wenn die Integration Kontodaten nur anzeigen oder exportieren muss.

Lesen und schreiben

Erlaubt GET- und Schreibendpunkte. Verwende sie nur für eine vertrauenswürdige Serverintegration, die die Sammlung oder das Sealed-Transaktionsjournal ändern muss.

Die API sieht nur Daten, die mit dem Lunidex-Konto in Neon synchronisiert wurden. Daten, die nur auf einem Gerät liegen, sind nicht verfügbar. Die Kontoidentität enthält keine E-Mail-Adresse.

Pagination und Filter

Listen liefern data und meta.nextCursor. Übernimm den Cursor unverändert für die nächste Seite und beende die Abfrage, wenn er null ist.

  • Die Standardseitengröße beträgt 25, maximal sind 100 möglich. Die Katalogsuche liefert höchstens 24 Ergebnisse pro Seite.
  • Karten akzeptieren die Filter language und set. Sealed-Positionen akzeptieren language und productId; Transaktionen akzeptieren language, productId, type, includeVoided und voided. Die Katalogsuche akzeptiert q und cursor.
  • Ein Cursor kann veraltet sein, wenn sich Sammlung, Journal, Preise oder Filter ändern. Die API liefert dann 409. Starte ohne Cursor mit dem aktuellen Stand neu.
Alle Kartenseiten serverseitig abrufen
const API_BASE_URL = "https://lunidex.app/api/v1";
const apiKey = process.env.LUNIDEX_API_KEY;
let cursor = null;

if (!apiKey) throw new Error("Missing LUNIDEX_API_KEY");

do {
  const query = new URLSearchParams({ limit: "100" });
  if (cursor) query.set("cursor", cursor);

  const response = await fetch(API_BASE_URL + "/cards?" + query, {
    headers: { Authorization: "Bearer " + apiKey },
  });
  const result = await response.json();
  if (!response.ok) throw new Error(result.error?.code ?? "REQUEST_FAILED");

  console.log(result.data);
  cursor = result.meta?.nextCursor ?? null;
} while (cursor);

Endpunkte

Die Pfade sind relativ zur API-Basis-URL. Jede Zeile zeigt die erforderliche Berechtigung und die im OpenAPI-Vertrag aufgeführten Parameter. Die maschinenlesbare Spezifikation enthält vollständige Schemas, Anfrage- und Antwortbeispiele sowie Fehler pro Operation; die folgenden Abschnitte erklären die wichtigsten Abläufe.

Konto und Vertrag

  • GETÖffentlich · kein Schlüssel
    /openapi.json

    Öffentlicher maschinenlesbarer Vertrag; kein API-Schlüssel erforderlich.

    Parameter:Keine
  • GETNur-Lese-Schlüssel
    /me

    Begrenzte Kontoidentität ohne E-Mail-Adresse.

    Parameter:Keine
  • GETNur-Lese-Schlüssel
    /summary

    Wichtige Kontostatistiken, physische und unterschiedliche Kartenzahlen sowie Sealed-Summen in EUR-Cent.

    Parameter:Keine

Kartensammlung

  • GETNur-Lese-Schlüssel
    /cards

    Paginierte Kartenbestände mit optionalen Sprach- und Set-Filtern.

    Parameter:
    • cursor(Abfrage)
    • limit(Abfrage)
    • language(Abfrage)
    • set(Abfrage)
  • GETNur-Lese-Schlüssel
    /cards/{cardId}

    TCGdex-Metadaten und eigene Bestände; keine Kartenbewertungen.

    Parameter:
    • cardId(Pfad)· erforderlich
    • language(Abfrage)
  • PUTLese-/Schreibschlüssel
    /cards/{cardId}

    Setzt die absolute Menge für Sprache und Kartenvariante.

    Parameter:
    • cardId(Pfad)· erforderlich

Sealed-Produkte und Transaktionen

  • GETNur-Lese-Schlüssel
    /sealed/catalogue

    Durchsucht den vorhandenen Cardmarket-Katalog für Sealed-Produkte.

    Parameter:
    • q(Abfrage)
    • cursor(Abfrage)
  • GETNur-Lese-Schlüssel
    /sealed/positions

    Aktuelle paginierte Positionen mit Kosten und gespeichertem Marktwert.

    Parameter:
    • cursor(Abfrage)
    • limit(Abfrage)
    • language(Abfrage)
    • productId(Abfrage)
  • GETNur-Lese-Schlüssel
    /sealed/positions/{productId}

    Aktuelle Sprachpositionen für ein besessenes Produkt.

    Parameter:
    • productId(Pfad)· erforderlich
  • GETNur-Lese-Schlüssel
    /sealed/transactions

    Privates Transaktionsjournal mit Revisionen und Filtern.

    Parameter:
    • cursor(Abfrage)
    • limit(Abfrage)
    • language(Abfrage)
    • productId(Abfrage)
    • type(Abfrage)
    • includeVoided(Abfrage)
    • voided(Abfrage)
  • POSTLese-/Schreibschlüssel
    /sealed/transactions

    Erstellt Kauf, Verkauf oder Tausch; Idempotency-Key erforderlich.

    Parameter:
    • Idempotency-Key(Header)· erforderlich
  • GETNur-Lese-Schlüssel
    /sealed/transactions/{id}

    Liest eine Transaktion dieses Kontos.

    Parameter:
    • id(Pfad)· erforderlich
  • PATCHLese-/Schreibschlüssel
    /sealed/transactions/{id}

    Ändert eine Transaktion mit Revisionsprüfung.

    Parameter:
    • id(Pfad)· erforderlich
  • POSTLese-/Schreibschlüssel
    /sealed/transactions/{id}/void

    Storniert eine Transaktion mit Revisionsprüfung.

    Parameter:
    • id(Pfad)· erforderlich

Karten

GET /cards listet besessene Varianten auf, ohne den vollständigen TCGdex-Katalog zu laden. Jeder Bestand enthält cardId, setId, language, variant und quantity; die optionalen Filter sind language und set.

GET /cards/{cardId} liefert nützliche TCGdex-Metadaten und die Bestände dieser Karte im Konto. Mit dem Abfrageparameter language kannst du die Bestände eingrenzen.

PUT /cards/{cardId} erwartet language, variant (unspecified, normal, reverse oder holo) und eine absolute Menge von 0 bis 10.000. Mit 0 wird die Variante entfernt.

PUT /cards/{cardId} · application/json
{
  "language": "en",
  "variant": "reverse",
  "quantity": 2
}

Ältere Bestände ohne Sprache bleiben sichtbar; language kann null sein und der Eintrag kann als legacy markiert sein.

Diese API gibt keine Kartenwerte zurück.

Sealed-Produkte

Sealed-Positionen enthalten Mengen nach Sprache, Kosten und aktuellen Wert auf Basis der bereits in Lunidex gespeicherten Preise. Beträge sind ganze EUR-Cent. Fehlt ein Preis, ist valueCents null und missingPrice gesetzt; die Zusammenfassung zählt außerdem missingPrices.

In GET /summary ist der Sealed-Wert null, wenn für mindestens ein besessenes Produkt kein Preis vorhanden ist; missingPrices enthält die Anzahl.

GET /sealed/catalogue

Suche mit q (maximal 150 Zeichen) und cursor nach Produkten im vorhandenen Cardmarket-Katalog. Jede Seite enthält höchstens 24 Produkte. Verwende die Produkt-ID in weiteren Anfragen.

GET /sealed/positions

GET /sealed/positions unterstützt language und productId. GET /sealed/positions/{productId} liefert die aktuelle Position eines Produkts. Diese Endpunkte zeigen aktuelle Summen, keine vollständige Preisverlaufserie.

GET /sealed/transactions

Das private Journal unterstützt language, productId, type (buy, sell, exchange), includeVoided und voided. Revisionen ermöglichen sichere Änderungen.

Schreibzugriffe, Idempotenz und Revisionen

Kartenmenge festlegen

PUT /cards/{cardId} setzt die Menge, statt einen Zuwachs zu addieren. Dieselbe Anfrage erneut zu senden lässt die Menge unverändert; mit 0 entfernst du die Variante. Vor dem Schreiben prüft die API Karte, Set, Sprache und verfügbare Variante.

Sealed-Transaktion erstellen

POST /sealed/transactions akzeptiert buy, sell und exchange. Der Header Idempotency-Key (8–200 Zeichen) und expectedRevision sind erforderlich. Verwende beim erneuten Senden derselben Anfrage denselben Schlüssel, damit keine zweite Bewegung entsteht. Der erste Aufruf erstellt die Transaktion; eine Wiederholung liefert das ursprüngliche Ergebnis.

Beispiel für einen Kauf · Produkt-ID und Revision durch aktuelle Werte ersetzen
curl --request POST "https://lunidex.app/api/v1/sealed/transactions" \
  -H "Authorization: Bearer $LUNIDEX_API_KEY" \
  -H "Idempotency-Key: lunidex-import-example-001" \
  -H "Content-Type: application/json" \
  --data '{
    "expectedRevision": 0,
    "kind": "buy",
    "cardmarketProductId": 12345,
    "language": "en",
    "date": "2026-09-27",
    "quantity": 1,
    "unitPriceCents": 2500
  }'

Lies vor dem Schreiben die aktuelle Journalrevision aus den Metadaten von GET /sealed/transactions und verwende sie als expectedRevision. PATCH /sealed/transactions/{id} benötigt zusätzlich die revision dieser Transaktion. Eine veraltete Revision führt zu 409. Lade Transaktion und Journal neu, bevor du über einen erneuten Versuch entscheidest.

PATCH /sealed/transactions/{id}

POST /sealed/transactions/{id}/void
POST /sealed/transactions/{id}/void benötigt revision und expectedRevision. Erstellen, Ändern und Stornieren verwenden dieselben Transaktionsprüfungen, Allokationen und Auditdaten wie Lunidex.

application/json
{
  "revision": 1,
  "expectedRevision": 0
}

Fehler und Antwortformat

Erfolgreiche Antworten verwenden { "data": ..., "meta": ... } (meta ist optional). Fehler verwenden { "error": { "code": ..., "message": ..., "details": ... } } (details ist optional).

Häufige Antworten: 400 ungültige Transaktionsdaten; 401 ungültiger Schlüssel; 403 fehlende Berechtigung; 404 Ressource fehlt oder gehört nicht zum Konto; 409 Cursor oder Revision veraltet; 410 Konto wird gelöscht; 422 ungültiger Abfrageparameter oder Request-Body; 429 Kontingent erreicht; 500 unerwarteter Serverfehler; 502 Kartendaten nicht verfügbar; 503 API nicht verfügbar.

Bei 409 rufe die aktuelle Ressource ab oder starte die Liste ohne Cursor neu. Wandle einen Konflikt nicht blind in eine neue Transaktion um.

Eine 429-Antwort enthält Retry-After. Warte mindestens die angegebene Anzahl Sekunden, bevor du es erneut versuchst.

Alle privaten API-Antworten verwenden Cache-Control: private, no-store. Das öffentliche OpenAPI-Dokument ist separat unter /api/v1/openapi.json verfügbar.

Kontingente

Die Limits gelten pro Lunidex-Konto und werden von dessen API-Schlüsseln gemeinsam genutzt.

Kontingente
Lesezugriffe pro Minute60
Schreibzugriffe pro Minute10
Lesezugriffe pro Tag1.000
Schreibzugriffe pro Tag100
Kartendetails pro Konto und Tag100
Sealed-Berechnungen pro Konto und Tag100
Globales Tageslimit pro kostenintensiver Kategorie5.000

Kartendetails und Sealed-Berechnungen haben außerdem getrennte globale Obergrenzen von jeweils 5.000 Vorgängen pro Tag. Eine 429-Antwort enthält Retry-After.

OpenAPI-Spezifikation öffnen