{"openapi":"3.0.3","info":{"title":"LiviGO Marketplace API","description":"API REST versionnée pour l’écosystème LiviGO (Diaspora algérienne, Marketplace multivendeurs, logistique LiviGO Hub & Delivery).","version":"0.1.0","contact":{"name":"Support LiviGO","url":"https://livigodelivery.com"}},"servers":[{"url":"/api/v1","description":"Serveur API v1 local ou distant"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Opaque"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"livigo_session"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"}},"required":["code","message","requestId"]}}},"Wilaya":{"type":"object","properties":{"code":{"type":"string","example":"25"},"name":{"type":"string","example":"Constantine"},"communes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"wilayaCode":{"type":"string"}}}}}},"Category":{"type":"object","properties":{"id":{"type":"string","example":"grocery"},"name":{"type":"string","example":"Courses & alimentation"},"slug":{"type":"string"},"icon":{"type":"string"},"subcategories":{"type":"array","items":{"type":"string"}}}},"Product":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"brand":{"type":"string"},"categoryId":{"type":"string"},"description":{"type":"string"},"image":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"offers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"storeId":{"type":"string"},"priceDzdCentimes":{"type":"integer"},"priceEurCents":{"type":"integer"},"stock":{"type":"integer"},"active":{"type":"boolean"}}}}}},"Order":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"status":{"type":"string"},"subtotalCents":{"type":"integer"},"deliveryCents":{"type":"integer","default":300},"totalCents":{"type":"integer"},"refundedCents":{"type":"integer"},"rateSnapshot":{"type":"string"},"scheduledFor":{"type":"string","format":"date-time"},"recipient":{"type":"object"},"items":{"type":"array","items":{"type":"object"}},"subOrders":{"type":"array","items":{"type":"object"}}}}}},"paths":{"/auth/password/forgot":{"post":{"summary":"Envoyer un lien personnel via le proxy email LiviGO","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"202":{"description":"Réponse identique pour une adresse connue ou inconnue, sans exposer le lien"},"429":{"description":"Trop de tentatives"}}}},"/auth/password/reset":{"post":{"summary":"Consommer un lien valable 30 minutes et révoquer les anciennes sessions","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string"},"password":{"type":"string","minLength":12}}}}}},"responses":{"200":{"description":"Mot de passe modifié"},"400":{"description":"Lien expiré ou déjà utilisé"}}}},"/users/delivery-preferences":{"get":{"summary":"Retrouver la destination et la plage horaire du compte","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"recipientId, recipient, wilayaCode et timeWindow"}}},"patch":{"summary":"Enregistrer les préférences, avec vérification du propriétaire du bénéficiaire","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientId":{"type":"string","nullable":true},"wilayaCode":{"type":"string","enum":["16","31","25"]},"timeWindow":{"type":"string","enum":["ANY","MORNING","AFTERNOON","EVENING"]}}}}}},"responses":{"200":{"description":"Préférences conservées côté serveur"},"404":{"description":"Bénéficiaire absent ou appartenant à un autre compte"},"409":{"description":"Bénéficiaire et wilaya incompatibles"}}}},"/merchant/ledger":{"get":{"summary":"Écritures de la boutique authentifiée, pages de 24 lignes","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1}},{"name":"days","in":"query","schema":{"type":"string","enum":["7","30","all"]}}],"responses":{"200":{"description":"items, total, page et limit"}}}},"/merchant/ledger/export":{"get":{"summary":"Export CSV complet de la période, jusqu’à 10000 écritures","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Nom du fichier et texte CSV"},"400":{"description":"Période trop large, sélectionner une période plus courte"}}}},"/admin/emails":{"get":{"summary":"États des 100 envois récents et compteurs globaux, sans corps ni liens privés","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"items, counts, configured et local"},"403":{"description":"Administrateur requis"}}}},"/admin/emails/retry":{"post":{"summary":"Relancer un échec email encore valide","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Résultat de la nouvelle tentative"},"409":{"description":"Envoi terminé ou expiré"}}}},"/health":{"get":{"summary":"État de santé du backend","responses":{"200":{"description":"Service opérationnel"}}}},"/openapi":{"get":{"summary":"Documentation OpenAPI","responses":{"200":{"description":"Spécification OpenAPI JSON"}}}},"/locations":{"get":{"summary":"Liste des wilayas de lancement (Alger 16, Oran 31, Constantine 25) et de leurs communes","responses":{"200":{"description":"Liste des wilayas"}}}},"/categories":{"get":{"summary":"Les 10 catégories officielles LiviGO et leurs sous-catégories","responses":{"200":{"description":"Liste des catégories"}}}},"/products":{"get":{"summary":"Catalogue public de produits filtré par wilaya, catégorie et mot-clé","parameters":[{"name":"wilaya","in":"query","schema":{"type":"string","default":"25"}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":12}}],"responses":{"200":{"description":"Produits avec offres marchands et conversion EUR"}}}},"/products/{id}":{"get":{"summary":"Fiche produit et offres de boutiques validées ouvertes dans la wilaya choisie","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"wilaya","in":"query","schema":{"type":"string","default":"25","enum":["16","31","25"]}}],"responses":{"200":{"description":"Produit et prix en EUR au taux actuel"},"404":{"description":"Aucun produit disponible dans ce périmètre"}}}},"/delivery-slots":{"get":{"summary":"Créneaux de livraison disponibles par wilaya","parameters":[{"name":"wilaya","in":"query","schema":{"type":"string","default":"25"}}],"responses":{"200":{"description":"Créneaux de livraison"}}}},"/auth/register":{"post":{"summary":"Inscription d’un nouveau compte client","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password"]}}}},"responses":{"201":{"description":"Compte créé avec session"}}}},"/auth/login":{"post":{"summary":"Connexion par email et mot de passe","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"]}}}},"responses":{"200":{"description":"Session active"}}}},"/auth/demo":{"post":{"summary":"Connexion rapide de démonstration (client, admin ou marchand)","responses":{"200":{"description":"Session de démonstration créée"}}}},"/orders/quote":{"post":{"summary":"Chiffrage et vérification de faisabilité logistique d’un panier multivendeurs","responses":{"200":{"description":"Devis avec sous-total, 3 € de livraison fixe et conversion EUR"}}}},"/orders":{"post":{"summary":"Création ou reprise idempotente, avec réservation et paiement Stripe ou simulation de développement","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":160}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipientId","slotId","lines"],"properties":{"recipientId":{"type":"string"},"slotId":{"type":"string"},"lines":{"type":"array","minItems":1,"maxItems":60,"items":{"type":"object","required":["offerId","quantity"],"properties":{"offerId":{"type":"string"},"quantity":{"type":"integer","minimum":1,"maximum":50}}}},"expectedTotalCents":{"type":"integer"},"expectedRate":{"type":"string"},"note":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Commande créée ou retrouvée. AWAITING_PAYMENT avec checkoutPayment pour PaymentSheet tant que Stripe n’est pas confirmé. Le clientSecret est privé et ne doit pas être persisté."},"409":{"description":"Conflit de devis, de stock, d’identité ou paiement annulé ; conserver une tentative financière ambiguë."},"503":{"description":"Configuration absente ou résultat réseau incertain ; reprendre avec la même clé et le même corps."}}},"get":{"summary":"Liste des commandes du client connecté ou toutes pour l’administration","responses":{"200":{"description":"Historique des commandes"}}}},"/payments/config":{"get":{"summary":"Configuration publique du paiement, sans secret serveur","responses":{"200":{"description":"provider STRIPE ou SIMULATION, test, demoEnabled et clé publishableKey si Stripe est configuré"},"503":{"description":"Configuration de paiement indisponible"}}}},"/webhooks/stripe":{"post":{"summary":"Événements Stripe snapshot avec vérification du corps brut et rapprochement canonique","security":[],"parameters":[{"name":"Stripe-Signature","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Événement traité ou sans rapport avec LiviGO Shop"},"401":{"description":"Signature absente, invalide ou trop ancienne"},"409":{"description":"Mode ou identité financière incohérent"},"503":{"description":"Rapprochement indisponible ; Stripe doit réessayer"}}}},"/orders/{id}/payment":{"post":{"summary":"Rapprochement du PaymentIntent auprès de Stripe, propriétaire ou Admin uniquement","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Commande avec son statut serveur ; une déclaration du client ne confirme jamais un paiement"},"404":{"description":"Commande introuvable dans ce périmètre"}}}},"/orders/{id}/cancel-payment":{"post":{"summary":"Annulation Stripe et libération confirmée, ou confirmation d’un paiement déjà réussi","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Commande CANCELLED ou déjà payée ; ne pas présumer l’annulation avant la réponse"},"503":{"description":"État incertain, réservation conservée"}}}},"/orders/{id}/refunds":{"post":{"summary":"Demande idempotente de remboursement d’un article payé et indisponible","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemId"],"properties":{"itemId":{"type":"string"}}}}}},"responses":{"200":{"description":"Remboursement PENDING ou CONFIRMED, montant serveur hors frais de livraison"},"409":{"description":"Article non éligible ou remboursement échoué nécessitant l’assistance"}}}}}}