Aller au contenu principal
Retour à la documentation

API publique Lunidex v1

Connectez un service serveur aux données déjà synchronisées avec un compte Lunidex. Créez une clé et envoyez une première requête, puis explorez les cartes, les produits scellés, la pagination et les écritures.

URL de basehttps://lunidex.app/api/v1
Gérer les clés APIOuvrir la spécification OpenAPI

Clés réservées aux serveurs

Ne placez jamais une clé API dans le code d’un navigateur, une application mobile, un dépôt public, une URL, une capture d’écran ou un journal partagé. Gardez-la dans un gestionnaire de secrets côté serveur et transmettez-la uniquement dans l’en-tête Authorization.

Démarrage rapide

Une intégration a besoin d’une clé, d’un secret stocké côté serveur et d’une requête HTTPS vers l’URL de base de l’API.

  1. 1

    Créer une clé

    Ouvrez la carte des clés API dans le tableau de bord Lunidex. Nommez la clé et choisissez lecture seule ou lecture et écriture. Le secret complet n’apparaît qu’une fois : copiez-le avant de fermer le message.

  2. 2

    Stocker le secret sur votre serveur

    Enregistrez la clé sous LUNIDEX_API_KEY dans le gestionnaire de secrets de votre hébergeur. Ne la commitez pas et ne l’envoyez pas au navigateur.

  3. 3

    Envoyer la première requête

    Appelez GET /me depuis votre serveur. Les routes privées exigent l’en-tête Authorization: Bearer suivi de la clé.

cURL · à exécuter dans un environnement serveur de confiance
curl --fail-with-body \
  -H "Authorization: Bearer $LUNIDEX_API_KEY" \
  https://lunidex.app/api/v1/me
Node.js · fetch côté serveur
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);

Authentification et permissions

Envoyez la clé dans l’en-tête HTTP Authorization pour chaque requête privée /api/v1. L’API renvoie du JSON dans une enveloppe data ; les réponses privées utilisent Cache-Control: private, no-store.

Lecture

Autorise les routes GET. Choisissez cette permission si l’intégration doit seulement afficher ou exporter les données du compte.

Lecture et écriture

Autorise les routes GET et les routes d’écriture. Réservez-la à une intégration serveur de confiance qui doit modifier la collection ou le journal des scellés.

L’API ne voit que les données synchronisées avec le compte Lunidex dans Neon. Les données présentes uniquement sur un appareil ne sont pas accessibles. L’identité du compte ne contient pas l’adresse e-mail.

Pagination et filtres

Les listes renvoient data et meta.nextCursor. Transmettez ce curseur sans le modifier pour obtenir la page suivante, puis arrêtez-vous quand il vaut null.

  • La taille par défaut est de 25 éléments et le maximum de 100. Le catalogue est limité à 24 résultats par page.
  • Les cartes acceptent les filtres language et set. Les positions scellées acceptent language et productId ; les transactions acceptent language, productId, type, includeVoided et voided. Le catalogue accepte q et cursor.
  • Un curseur peut devenir obsolète si la collection, le journal, les prix ou les filtres changent. L’API renvoie 409 ; recommencez sans curseur avec les données à jour.
Lire toutes les pages de cartes depuis un serveur
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);

Routes

Les chemins sont relatifs à l’URL de base de l’API. Chaque ligne indique le niveau d’accès et les paramètres du contrat OpenAPI. La spécification lisible par machine contient les schémas complets, des exemples de requête et de réponse, ainsi que les erreurs par opération ; les sections ci-dessous expliquent les principaux usages.

Compte et contrat

  • GETPublique · sans clé
    /openapi.json

    Contrat lisible par machine, public et sans clé API.

    Paramètres:Aucun
  • GETClé lecture seule
    /me

    Identité limitée du compte, sans adresse e-mail.

    Paramètres:Aucun
  • GETClé lecture seule
    /summary

    Statistiques principales, nombre physique et distinct de cartes, et totaux des scellés en centimes EUR.

    Paramètres:Aucun

Collection de cartes

  • GETClé lecture seule
    /cards

    Possessions paginées, avec filtres facultatifs de langue et de set.

    Paramètres:
    • cursor(requête)
    • limit(requête)
    • language(requête)
    • set(requête)
  • GETClé lecture seule
    /cards/{cardId}

    Métadonnées TCGdex et possessions du compte, sans valorisation des cartes.

    Paramètres:
    • cardId(chemin)· obligatoire
    • language(requête)
  • PUTClé lecture-écriture
    /cards/{cardId}

    Définit la quantité absolue pour une langue et une variante.

    Paramètres:
    • cardId(chemin)· obligatoire

Produits scellés et transactions

  • GETClé lecture seule
    /sealed/catalogue

    Recherche dans le catalogue existant de produits scellés Cardmarket.

    Paramètres:
    • q(requête)
    • cursor(requête)
  • GETClé lecture seule
    /sealed/positions

    Positions actuelles paginées, coût et valeur de marché stockée.

    Paramètres:
    • cursor(requête)
    • limit(requête)
    • language(requête)
    • productId(requête)
  • GETClé lecture seule
    /sealed/positions/{productId}

    Positions par langue pour un produit possédé.

    Paramètres:
    • productId(chemin)· obligatoire
  • GETClé lecture seule
    /sealed/transactions

    Journal privé avec révisions et filtres.

    Paramètres:
    • cursor(requête)
    • limit(requête)
    • language(requête)
    • productId(requête)
    • type(requête)
    • includeVoided(requête)
    • voided(requête)
  • POSTClé lecture-écriture
    /sealed/transactions

    Crée un achat, une vente ou un échange ; clé d’idempotence requise.

    Paramètres:
    • Idempotency-Key(en-tête)· obligatoire
  • GETClé lecture seule
    /sealed/transactions/{id}

    Lit une transaction appartenant à ce compte.

    Paramètres:
    • id(chemin)· obligatoire
  • PATCHClé lecture-écriture
    /sealed/transactions/{id}

    Modifie une transaction avec contrôle de révision.

    Paramètres:
    • id(chemin)· obligatoire
  • POSTClé lecture-écriture
    /sealed/transactions/{id}/void

    Annule une transaction avec contrôle de révision.

    Paramètres:
    • id(chemin)· obligatoire

Cartes

GET /cards liste les variantes possédées sans charger tout le catalogue TCGdex. Chaque possession contient cardId, setId, language, variant et quantity ; les filtres facultatifs sont language et set.

GET /cards/{cardId} renvoie les métadonnées TCGdex utiles et les possessions de cette carte dans le compte. Ajoutez le paramètre language pour limiter les possessions.

PUT /cards/{cardId} reçoit language, variant (unspecified, normal, reverse ou holo) et une quantité absolue comprise entre 0 et 10 000. La valeur 0 retire cette variante.

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

Les anciennes possessions sans langue restent visibles ; leur langue peut valoir null et elles peuvent être marquées legacy.

Cette API ne renvoie jamais la valeur des cartes.

Produits scellés

Les positions indiquent les quantités par langue, le coût et la valeur actuelle calculée à partir des prix déjà stockés dans Lunidex. Les montants sont des centimes entiers d’EUR. Si un prix manque, valueCents vaut null et missingPrice l’indique ; le résumé compte aussi missingPrices.

Dans GET /summary, la valeur des scellés vaut null si le prix manque pour au moins un produit possédé ; missingPrices donne le nombre de prix manquants.

GET /sealed/catalogue

Recherchez les produits déjà présents au catalogue Cardmarket avec q (150 caractères maximum) et cursor. Chaque page contient au plus 24 produits ; utilisez leur ID dans les requêtes suivantes.

GET /sealed/positions

GET /sealed/positions accepte language et productId. GET /sealed/positions/{productId} renvoie la position actuelle d’un produit. Ces routes exposent les totaux actuels, sans série complète d’historique des prix.

GET /sealed/transactions

Le journal privé accepte les filtres language, productId, type (buy, sell, exchange), includeVoided et voided. Il renvoie des révisions pour permettre des modifications sûres.

Écritures, idempotence et révisions

Définir une quantité de cartes

PUT /cards/{cardId} définit une quantité au lieu d’ajouter un delta. Répéter la même requête conserve la même quantité ; envoyez 0 pour retirer la variante. Avant l’écriture, l’API vérifie la carte, le set, la langue et la variante disponible.

Créer une transaction scellée

POST /sealed/transactions accepte les mouvements buy, sell et exchange. Il exige l’en-tête Idempotency-Key (8 à 200 caractères) et expectedRevision. Réutilisez la même clé pour réessayer la même requête afin de ne pas créer un second mouvement. La première requête crée la transaction ; une répétition renvoie le résultat d’origine.

Exemple de corps pour un achat · remplacez l’ID produit et la révision par leurs valeurs actuelles
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
  }'

Avant une écriture, lisez la dernière révision du journal dans les métadonnées de GET /sealed/transactions et utilisez-la comme expectedRevision. PATCH /sealed/transactions/{id} exige aussi la revision de cette transaction. Une révision obsolète renvoie 409 ; rechargez la transaction et le journal avant de décider de réessayer.

PATCH /sealed/transactions/{id}

POST /sealed/transactions/{id}/void
POST /sealed/transactions/{id}/void exige revision et expectedRevision. Création, modification et annulation réutilisent les validations, allocations et l’audit des transactions Lunidex.

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

Erreurs et format des réponses

Une réponse réussie utilise { "data": ..., "meta": ... } (meta est facultatif). Une erreur utilise { "error": { "code": ..., "message": ..., "details": ... } } (details est facultatif).

Réponses courantes : 400 données de transaction invalides ; 401 clé invalide ; 403 permission insuffisante ; 404 ressource absente ou non détenue ; 409 curseur ou révision obsolète ; 410 compte en cours de suppression ; 422 paramètre de requête ou corps invalide ; 429 quota atteint ; 500 erreur serveur inattendue ; 502 données de carte indisponibles ; 503 API indisponible.

Après un 409, récupérez la ressource à jour ou recommencez la liste sans curseur. Ne transformez pas aveuglément un conflit en nouvelle transaction.

Une réponse 429 inclut Retry-After. Attendez au moins le nombre de secondes indiqué avant de réessayer.

Toutes les réponses privées utilisent Cache-Control: private, no-store. La spécification OpenAPI publique est disponible séparément sur /api/v1/openapi.json.

Quotas

Les limites s’appliquent par compte Lunidex et sont partagées entre ses clés API.

Quotas
Lectures par minute60
Écritures par minute10
Lectures par jour1 000
Écritures par jour100
Détails de cartes par compte et par jour100
Calculs de scellés par compte et par jour100
Plafond global quotidien par catégorie coûteuse5 000

Les détails de cartes et les calculs de scellés ont aussi chacun un plafond global distinct de 5 000 opérations par jour. Une réponse 429 inclut Retry-After.

Ouvrir la spécification OpenAPI