{"openapi":"3.1.0","info":{"title":"API InvexQR","version":"2026-10","description":"API REST d’InvexQR (INVEXCAM SARL). Réservée à la formule Business.\n\nAuthentification : `Authorization: Bearer iqr_live_…` (clé créée dans Espace client → Développeurs). Réponses JSON en UTF-8, dates ISO 8601 en UTC, montants en FCFA (XAF). Messages d’erreur en français, codes d’erreur stables.\n\nLimites : débit par clé (en-têtes `X-RateLimit-*`) et quota mensuel par organisation (`X-Quota-*`). Les créations acceptent l’en-tête `Idempotency-Key` pour pouvoir être rejouées sans doublon.","license":{"name":"Propriétaire — INVEXCAM SARL","url":"https://invexqr.com/developpeurs"},"contact":{"name":"INVEXCAM SARL","email":"infos@invexcam.com","url":"https://invexqr.com/developpeurs"}},"servers":[{"url":"https://invexqr.com/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Plateforme","description":"Informations publiques sur la plateforme, sans authentification."},{"name":"Compte","description":"Organisation, clé utilisée, consommation et domaines personnalisés."},{"name":"QR codes","description":"QR dynamiques et statiques, images, historique, création en masse."},{"name":"MultiLink","description":"Pages de liens mobiles (WhatsApp, réseaux sociaux, site…) et leur QR code."},{"name":"Campagnes","description":"Campaign Manager : supports, QR existants associés, UTM, conversions mesurées et déclarées."},{"name":"Statistiques","description":"Scans, visites et clics réels, filtrés des robots et doublons ; export des événements."},{"name":"Webhooks","description":"Notifications signées envoyées à vos serveurs."}],"paths":{"/public/stats":{"get":{"tags":["Plateforme"],"operationId":"getPublicStats","summary":"Compteurs publics de la plateforme","description":"Totaux agrégés et anonymes (QR codes, scans, comptes, organisations actives, campagnes, intégrations, requêtes API), calculés sur les données réelles et mis en cache. Chaque compteur est accompagné de sa définition. Un compteur sous son seuil de publication n’est pas renvoyé. Sans authentification ; 30 requêtes par minute et par visiteur.","responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"computed_at":{"type":["string","null"],"format":"date-time"},"refresh_minutes":{"type":"integer"},"counters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["qr_codes","scans","users","active_orgs","campaigns","integrations","api_calls"]},"label":{"type":"string"},"value":{"type":"integer","minimum":1},"definition":{"type":"string"}}}},"methodology":{"type":"string","format":"uri"}}}}}}}},"429":{"$ref":"#/components/responses/E429"}},"security":[]}},"/me":{"get":{"tags":["Compte"],"operationId":"getMe","summary":"Organisation, clé et consommation","description":"Retourne l’organisation liée à la clé, ses permissions, la consommation du mois et les quotas utiles.","responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Me"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}}}},"/domains":{"get":{"tags":["Compte"],"operationId":"listDomains","summary":"Domaines personnalisés","description":"Domaines déclarés par l’organisation. Seuls les domaines `active` peuvent être utilisés à la création d’un QR.\n\nPermission requise : `qr:read`.","x-scope":"qr:read","responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}}}},"/qr":{"get":{"tags":["QR codes"],"operationId":"listQr","summary":"Lister les QR codes","description":"Liste paginée, du plus récent au plus ancien.\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"page","in":"query","required":false,"description":"Page (à partir de 1).","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Éléments par page (200 au maximum).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"kind","in":"query","required":false,"description":"Type de QR.","schema":{"type":"string","enum":["static","redirect","multilink","file","coupon"]}},{"name":"status","in":"query","required":false,"description":"Statut.","schema":{"type":"string","enum":["active","paused","suspended"]}},{"name":"campaign_id","in":"query","required":false,"description":"QR associés en ce moment à une campagne (supports et QR existants).","schema":{"type":"integer"}},{"name":"batch_id","in":"query","required":false,"description":"Codes d’un lot créé en masse.","schema":{"type":"integer"}},{"name":"search","in":"query","required":false,"description":"Recherche dans le nom (ou identifiant court exact).","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/QrCode"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"422":{"$ref":"#/components/responses/E422"}}},"post":{"tags":["QR codes"],"operationId":"createQr","summary":"Créer un QR code","description":"QR dynamique de redirection (`kind: redirect`, par défaut) ou statique (`kind: static`, contenu encodé tel quel).\n\nAvec `campaign_id`, le QR devient un support de la campagne : UTM ajoutés automatiquement (`channel` = type de support).\n\nLes quotas de la formule s’appliquent comme dans l’interface.\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCreate"},"example":{"name":"Affiche vitrine Akwa","destination":"https://boutique.exemple.cm/promo","style":{"fg":"#231F20","frame":"bottom","frameText":"Scannez-moi"}}}}},"responses":{"201":{"description":"QR créé","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/QrCode"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/qr/bulk":{"post":{"tags":["QR codes"],"operationId":"bulkCreateQr","summary":"Créer des QR codes en masse","description":"Tout ou rien : si une ligne est invalide, aucun code n’est créé et les erreurs sont détaillées par ligne (`items.N.destination`). Le nombre de lignes est limité par la formule (`max_bulk_rows`).\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreate"},"example":{"mode":"dynamic","name":"Chevalets des tables","items":[{"name":"Table 1","destination":"https://resto.exemple.cm/menu?table=1"},{"name":"Table 2","destination":"https://resto.exemple.cm/menu?table=2"}]}}}},"responses":{"201":{"description":"Lot créé","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/QrCode"}},"meta":{"type":"object","properties":{"batch_id":{"type":"integer"},"count":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/qr/{id}":{"get":{"tags":["QR codes"],"operationId":"getQr","summary":"Détail d’un QR code","description":"Pour une page MultiLink, la réponse inclut aussi `page` et `links`.\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/QrCode"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}},"patch":{"tags":["QR codes"],"operationId":"updateQr","summary":"Modifier un QR code","description":"Mise à jour partielle : `name`, `destination` (QR de redirection : le code imprimé redirige aussitôt vers la nouvelle adresse, changement historisé), `status` (`active` ou `paused`), `style`, `expires_at` (ISO 8601, `null` pour retirer) et `expired_url`.\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrUpdate"},"example":{"destination":"https://boutique.exemple.cm/nouvelle-promo"}}}},"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/QrCode"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}},"delete":{"tags":["QR codes"],"operationId":"deleteQr","summary":"Supprimer un QR code","description":"Suppression définitive : un QR dynamique imprimé affichera « page introuvable ».\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Supprimé"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/qr/{id}/image":{"get":{"tags":["QR codes"],"operationId":"getQrImage","summary":"Image du QR code","description":"Rendu serveur avec le style enregistré (couleurs, dégradé, formes, logo, cadre). Format QR Code uniquement. Réponse mise en cache (ETag).\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"format","in":"query","required":false,"description":"Format de l’image.","schema":{"type":"string","enum":["png","svg"],"default":"png"}},{"name":"size","in":"query","required":false,"description":"Largeur en pixels (64 à 2000).","schema":{"type":"integer","minimum":64,"maximum":2000,"default":1024}},{"name":"margin","in":"query","required":false,"description":"Marge en modules (0 à 10).","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"ecc","in":"query","required":false,"description":"Niveau de correction d’erreur minimal.","schema":{"type":"string","enum":["L","M","Q","H"]}},{"name":"frame","in":"query","required":false,"description":"`none` pour retirer le cadre.","schema":{"type":"string","enum":["none"]}}],"responses":{"200":{"description":"Image","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/svg+xml":{"schema":{"type":"string"}}}},"304":{"description":"Non modifiée (If-None-Match)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/qr/{id}/history":{"get":{"tags":["QR codes"],"operationId":"getQrHistory","summary":"Historique des destinations","description":"Les 200 derniers changements (destination et liens MultiLink).\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEntry"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/qr/{id}/campaigns":{"get":{"tags":["QR codes"],"operationId":"getQrCampaigns","summary":"Campagnes d’un QR code","description":"Associations du QR (en cours et passées) avec les scans attribués à chaque campagne, et le total global du QR (toutes périodes, toutes campagnes), qu’une association ne modifie jamais.\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"qr_id":{"type":"integer"},"global_scans":{"type":"integer"},"campaigns":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/CampaignQr"},{"type":"object","properties":{"campaign_name":{"type":"string"},"attributed_scans":{"type":"integer"}}}]}}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/qr/{id}/stats":{"get":{"tags":["Statistiques"],"operationId":"getQrStats","summary":"Statistiques d’un QR code","description":"Scans filtrés (robots, aperçus, doublons exclus), visiteurs uniques estimés, clics, répartitions.\n\nPermission requise : `stats:read`.","x-scope":"stats:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"from","in":"query","required":false,"description":"Premier jour (AAAA-MM-JJ, fuseau de l’organisation). Défaut : il y a 29 jours.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Dernier jour inclus (AAAA-MM-JJ). Défaut : aujourd’hui.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Stats"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/multilink":{"post":{"tags":["MultiLink"],"operationId":"createMultilink","summary":"Créer une page MultiLink","description":"Crée la page et son QR code. `links` est facultatif ; chaque lien a un `type` (whatsapp, facebook, instagram, tiktok, site, telephone…) et une `value` en saisie simple (numéro, @pseudo, adresse).\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultilinkCreate"},"example":{"title":"Boutique Ndolo","bio":"Mode et accessoires à Douala","theme":"classique","links":[{"type":"whatsapp","value":"+237 655 44 18 48"},{"type":"instagram","value":"@boutiquendolo"},{"type":"website","value":"boutique-ndolo.cm"}]}}}},"responses":{"201":{"description":"Page créée","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MultilinkPage"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/multilink/{id}":{"get":{"tags":["MultiLink"],"operationId":"getMultilink","summary":"Détail d’une page MultiLink","description":"\n\nPermission requise : `qr:read`.","x-scope":"qr:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MultilinkPage"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}},"patch":{"tags":["MultiLink"],"operationId":"updateMultilink","summary":"Modifier une page MultiLink","description":"`links` remplace la liste complète dans l’ordre fourni. Indiquez l’`id` d’un lien existant pour le conserver : ses statistiques et son adresse de clic sont préservées.\n\nPermission requise : `qr:write`.","x-scope":"qr:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultilinkUpdate"}}}},"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MultilinkPage"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/campaigns":{"get":{"tags":["Campagnes"],"operationId":"listCampaigns","summary":"Lister les campagnes","description":"\n\nPermission requise : `campaigns:read`.","x-scope":"campaigns:read","parameters":[{"name":"page","in":"query","required":false,"description":"Page (à partir de 1).","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Éléments par page (200 au maximum).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"status","in":"query","required":false,"description":"Statut.","schema":{"type":"string","enum":["active","paused","archived","draft"]}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}}},"post":{"tags":["Campagnes"],"operationId":"createCampaign","summary":"Créer une campagne","description":"Associez ensuite des QR existants avec `POST /campaigns/{id}/qr`, ou créez un support (QR) par emplacement avec `POST /campaigns/{id}/supports`.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignInput"},"example":{"name":"Rentrée 2026","objective":"ventes","destination":"https://boutique.exemple.cm/rentree","budget":250000,"conversion_tracking":true}}}},"responses":{"201":{"description":"Campagne créée","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Campaign"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/campaigns/{id}":{"get":{"tags":["Campagnes"],"operationId":"getCampaign","summary":"Campagne et performance","description":"Performance par QR sur la période (supports et QR existants associés) : scans attribués, visiteurs, conversions, valeur, coût par scan et par conversion, ROI (calculés seulement si les données existent), et total global de chaque QR.\n\nPermission requise : `campaigns:read`.","x-scope":"campaigns:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"from","in":"query","required":false,"description":"Premier jour (AAAA-MM-JJ, fuseau de l’organisation). Défaut : il y a 29 jours.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Dernier jour inclus (AAAA-MM-JJ). Défaut : aujourd’hui.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CampaignDetail"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}},"patch":{"tags":["Campagnes"],"operationId":"updateCampaign","summary":"Modifier une campagne","description":"Mise à jour partielle. Changer la destination ou les UTM recalcule la destination des supports (historisé). `status` : active, paused, archived.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdate"}}}},"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CampaignDetail"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/campaigns/{id}/supports":{"post":{"tags":["Campagnes"],"operationId":"addCampaignSupport","summary":"Ajouter un support (QR)","description":"Crée un QR dynamique distinct pour un emplacement (affiche, flyer…), avec ses UTM.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channel"],"properties":{"channel":{"type":"string","enum":["affiche","flyer","depliant","carte_visite","emballage","etiquette","facture","ticket_caisse","vitrine","panneau","presse","television","radio","evenement","salon","sms","email","reseaux_sociaux","publicite_numerique","catalogue","menu","support_commercial"]},"label":{"type":"string","maxLength":80},"destination":{"type":"string","description":"Adresse propre au support (sinon celle de la campagne)."},"cost":{"type":"integer","description":"Coût du support en FCFA (impression, emplacement)."},"domain":{"type":"string"}}},"example":{"channel":"flyer","label":"Flyers marché Mboppi","cost":45000}}}},"responses":{"201":{"description":"Support créé","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/QrCode"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/campaigns/{id}/qr":{"get":{"tags":["Campagnes"],"operationId":"listCampaignQr","summary":"QR codes associés","description":"Supports et QR existants de la campagne. `state=all` inclut les associations passées (historique).\n\nPermission requise : `campaigns:read`.","x-scope":"campaigns:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"state","in":"query","required":false,"description":"Associations en cours ou toutes.","schema":{"type":"string","enum":["current","all"],"default":"current"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/CampaignQr"},{"type":"object","properties":{"qr":{"$ref":"#/components/schemas/QrCode"}}}]}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}},"post":{"tags":["Campagnes"],"operationId":"attachCampaignQr","summary":"Associer des QR codes existants","description":"Associe des QR de votre bibliothèque à la campagne, sans les recréer ni les modifier (même image, même lien, même destination). Leurs scans sont attribués à la campagne à partir de `counted_from` (`today` par défaut, `start` = début de la campagne, ou une date) ; leurs statistiques globales ne changent pas.\n\nUn QR déjà associé n’est pas dupliqué (`already_attached`). Un identifiant inexistant ou d’une autre organisation est listé dans `not_found` et n’est jamais associé. Seuls les QR dynamiques non suspendus sont associables (`refused`). Un même QR peut appartenir à plusieurs campagnes.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qr_ids"],"properties":{"qr_ids":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"integer"}},"counted_from":{"type":"string","description":"`today`, `start` ou AAAA-MM-JJ (entre le début de la campagne et aujourd’hui).","default":"today"},"pass_token":{"type":"boolean","default":false,"description":"Transmettre le jeton iqr à la destination (QR de redirection, campagne avec suivi des conversions)."},"cost":{"type":"integer","minimum":0,"description":"Coût par QR pour cette campagne, en FCFA."}}},"example":{"qr_ids":[311,312],"counted_from":"today"}}}},"responses":{"201":{"description":"Au moins un QR associé","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AttachResult"}}}}}},"200":{"description":"Aucun nouveau QR associé","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AttachResult"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"422":{"$ref":"#/components/responses/E422"}}}},"/campaigns/{id}/qr/{qr}":{"patch":{"tags":["Campagnes"],"operationId":"updateCampaignQr","summary":"Modifier une association","description":"`cost` (FCFA, `null` pour effacer) et `pass_token` d’une association en cours.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"qr","in":"path","required":true,"description":"Identifiant du QR","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cost":{"type":["integer","null"]},"pass_token":{"type":"boolean"}}},"example":{"cost":0}}}},"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CampaignQr"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}},"delete":{"tags":["Campagnes"],"operationId":"detachCampaignQr","summary":"Retirer un QR de la campagne","description":"Ferme l’association : le QR n’est ni supprimé ni modifié et ses statistiques globales restent intactes. Les scans déjà attribués restent dans l’historique de la campagne, sauf avec `void=true` (association faite par erreur : aucun jour attribué).\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"qr","in":"path","required":true,"description":"Identifiant du QR","schema":{"type":"integer","minimum":1}},{"name":"void","in":"query","required":false,"description":"Annuler l’attribution.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CampaignQr"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/campaigns/{id}/conversions":{"post":{"tags":["Campagnes"],"operationId":"addManualConversion","summary":"Déclarer une conversion","description":"Conversion déclarée (vente en boutique, commande par téléphone…), toujours distinguée des conversions mesurées.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","format":"date"},"quantity":{"type":"integer","minimum":1,"default":1},"value":{"type":"integer","minimum":0,"description":"Montant en FCFA."},"qr_id":{"type":"integer"},"note":{"type":"string","maxLength":255}}},"example":{"quantity":3,"value":45000,"note":"Ventes en boutique du samedi"}}}},"responses":{"201":{"description":"Conversion enregistrée","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Conversion"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/conversions":{"post":{"tags":["Campagnes"],"operationId":"trackConversion","summary":"Enregistrer une conversion mesurée","description":"À appeler par votre serveur quand un visiteur venu d’un scan convertit (commande payée…). `scan_token` est la valeur du paramètre `iqr` ajouté à l’adresse de destination. Une seule conversion par scan, dans les 30 jours. Rejouer l’appel renvoie `status: duplicate`.\n\nPermission requise : `campaigns:write`.","x-scope":"campaigns:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scan_token"],"properties":{"scan_token":{"type":"string"},"value":{"type":"integer","minimum":0},"quantity":{"type":"integer","minimum":1},"campaign_id":{"type":"integer","description":"Nécessaire seulement si le QR scanné faisait partie de plusieurs campagnes ce jour-là (erreur `campaign_required`)."}}},"example":{"scan_token":"2n9c.5f0e4c1a9b7d","value":15000}}}},"responses":{"201":{"description":"Conversion enregistrée","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/TrackedConversion"}}}}}},"200":{"description":"Déjà enregistrée (duplicate)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/TrackedConversion"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/stats":{"get":{"tags":["Statistiques"],"operationId":"getStats","summary":"Statistiques de l’organisation","description":"Totaux, série quotidienne, répartitions et classement des QR. Filtrable par QR ou campagne.\n\nPermission requise : `stats:read`.","x-scope":"stats:read","parameters":[{"name":"from","in":"query","required":false,"description":"Premier jour (AAAA-MM-JJ, fuseau de l’organisation). Défaut : il y a 29 jours.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Dernier jour inclus (AAAA-MM-JJ). Défaut : aujourd’hui.","schema":{"type":"string","format":"date"}},{"name":"qr_id","in":"query","required":false,"description":"Limiter à un QR.","schema":{"type":"integer"}},{"name":"campaign_id","in":"query","required":false,"description":"Limiter aux scans attribués à une campagne (chaque QR sur ses jours d’association).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Stats"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/stats/geo":{"get":{"tags":["Statistiques"],"operationId":"getGeoStats","summary":"Répartition géographique","description":"Pays et villes **estimés** à partir de l’adresse IP au moment du scan (base locale ; adresse non conservée, jamais transmise à un tiers). La ville n’est donnée que lorsque la précision le permet (pas pour les réseaux mobiles) ; VPN, relais et hébergeurs sont « inconnus ». `totals.scans` = `with_country` + `unknown` + `unmeasured` (scans antérieurs à la géolocalisation : jamais localisés après coup).\n\nPermission requise : `stats:read`.","x-scope":"stats:read","parameters":[{"name":"from","in":"query","required":false,"description":"Premier jour (AAAA-MM-JJ, fuseau de l’organisation). Défaut : il y a 29 jours.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Dernier jour inclus (AAAA-MM-JJ). Défaut : aujourd’hui.","schema":{"type":"string","format":"date"}},{"name":"qr_id","in":"query","required":false,"description":"Limiter à un QR.","schema":{"type":"integer"}},{"name":"campaign_id","in":"query","required":false,"description":"Limiter aux scans attribués à une campagne.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"period":{"type":"object"},"totals":{"type":"object","properties":{"scans":{"type":"integer"},"with_country":{"type":"integer"},"with_city":{"type":"integer"},"unknown":{"type":"integer"},"unmeasured":{"type":"integer"}}},"countries":{"type":"array","items":{"type":"object","properties":{"country_code":{"type":"string"},"country":{"type":"string"},"scans":{"type":"integer"},"scans_with_city":{"type":"integer"},"city_unavailable":{"type":"object"}}}},"cities":{"type":"array","items":{"type":"object","properties":{"country_code":{"type":"string"},"city":{"type":"string"},"scans":{"type":"integer"},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]}}}},"unknown_reasons":{"type":"object"},"method":{"type":"string"},"source":{"type":["string","null"],"description":"Mention de la source exigée par sa licence"}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/events":{"get":{"tags":["Statistiques"],"operationId":"exportEvents","summary":"Exporter les événements","description":"Événements bruts enregistrés par le serveur, sans aucune donnée personnelle (ni IP, ni empreinte visiteur). Par défaut, seuls les événements retenus (`status=valid`). Pagination par curseur : rappelez avec `after_id` = `meta.next_after_id` tant que `meta.has_more` vaut true.\n\nPermission requise : `stats:read`.","x-scope":"stats:read","parameters":[{"name":"from","in":"query","required":false,"description":"Premier jour (AAAA-MM-JJ, fuseau de l’organisation). Défaut : il y a 29 jours.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Dernier jour inclus (AAAA-MM-JJ). Défaut : aujourd’hui.","schema":{"type":"string","format":"date"}},{"name":"type","in":"query","required":false,"description":"Type.","schema":{"type":"string","enum":["scan","visit","click"]}},{"name":"status","in":"query","required":false,"description":"Classification (`all` pour tout).","schema":{"type":"string","enum":["valid","bot","preview","prefetch","duplicate","rate","all"],"default":"valid"}},{"name":"qr_id","in":"query","required":false,"description":"Limiter à un QR.","schema":{"type":"integer"}},{"name":"after_id","in":"query","required":false,"description":"Curseur.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Taille de page (1000 au maximum).","schema":{"type":"integer","maximum":1000,"default":500}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"422":{"$ref":"#/components/responses/E422"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"operationId":"listWebhooks","summary":"Lister les webhooks","description":"\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}}},"post":{"tags":["Webhooks"],"operationId":"createWebhook","summary":"Créer un webhook","description":"Le secret de signature (`secret`) n’est renvoyé qu’à la création ou à sa régénération : conservez-le. Adresse HTTPS publique obligatoire.\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri"},"events":{"oneOf":[{"type":"array","items":{"type":"string","enum":["scan.recorded","click.recorded","conversion.recorded","coupon.issued","coupon.redeemed","qr.created","qr.updated","qr.deleted","batch.created","campaign.created","campaign.updated","campaign.qr_attached","campaign.qr_detached","alert.triggered"]}},{"type":"string","const":"*"}]},"description":{"type":"string","maxLength":160}}},"example":{"url":"https://crm.exemple.cm/hooks/invexqr","events":["scan.recorded","conversion.recorded"]}}}},"responses":{"201":{"description":"Webhook créé","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookWithSecret"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"422":{"$ref":"#/components/responses/E422"}}}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"operationId":"getWebhook","summary":"Détail d’un webhook","description":"\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Webhook"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}},"patch":{"tags":["Webhooks"],"operationId":"updateWebhook","summary":"Modifier un webhook","description":"`status: active` réactive un webhook désactivé et remet son compteur d’échecs à zéro.\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["scan.recorded","click.recorded","conversion.recorded","coupon.issued","coupon.redeemed","qr.created","qr.updated","qr.deleted","batch.created","campaign.created","campaign.updated","campaign.qr_attached","campaign.qr_detached","alert.triggered"]}},"description":{"type":"string"},"status":{"type":"string","enum":["active","disabled"]}}}}}},"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Webhook"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}},"delete":{"tags":["Webhooks"],"operationId":"deleteWebhook","summary":"Supprimer un webhook","description":"\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Supprimé"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"operationId":"testWebhook","summary":"Envoyer un événement de test","description":"Envoie immédiatement `test.ping` et retourne le résultat de la livraison.\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Delivery"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/webhooks/{id}/secret":{"post":{"tags":["Webhooks"],"operationId":"rotateWebhookSecret","summary":"Régénérer le secret","description":"L’ancien secret cesse immédiatement d’être utilisé.\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookWithSecret"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"operationId":"listDeliveries","summary":"Journal des livraisons","description":"Conservé 30 jours.\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"description":"Page (à partir de 1).","schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Éléments par page (200 au maximum).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"status","in":"query","required":false,"description":"Statut.","schema":{"type":"string","enum":["pending","success","failed"]}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"422":{"$ref":"#/components/responses/E422"}}}},"/webhooks/{id}/deliveries/{did}/retry":{"post":{"tags":["Webhooks"],"operationId":"retryDelivery","summary":"Relancer une livraison","description":"Renvoie immédiatement l’événement (même identifiant, nouvelle signature).\n\nPermission requise : `webhooks:manage`.","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant","schema":{"type":"integer","minimum":1}},{"name":"did","in":"path","required":true,"description":"Identifiant de la livraison","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Succès","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Delivery"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"}}}}},"webhooks":{"scan.recorded":{"post":{"operationId":"eventScanRecorded","summary":"Scan ou visite comptabilisé (robots et doublons exclus)","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"click.recorded":{"post":{"operationId":"eventClickRecorded","summary":"Clic sur un lien MultiLink","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"conversion.recorded":{"post":{"operationId":"eventConversionRecorded","summary":"Conversion enregistrée (mesurée ou déclarée)","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"coupon.issued":{"post":{"operationId":"eventCouponIssued","summary":"Code de coupon délivré à un client","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"coupon.redeemed":{"post":{"operationId":"eventCouponRedeemed","summary":"Coupon validé en caisse","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"qr.created":{"post":{"operationId":"eventQrCreated","summary":"QR code créé","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"qr.updated":{"post":{"operationId":"eventQrUpdated","summary":"QR code modifié (destination, statut, nom)","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"qr.deleted":{"post":{"operationId":"eventQrDeleted","summary":"QR code supprimé","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"batch.created":{"post":{"operationId":"eventBatchCreated","summary":"Lot de QR codes créé en masse","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"campaign.created":{"post":{"operationId":"eventCampaignCreated","summary":"Campagne créée","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"campaign.updated":{"post":{"operationId":"eventCampaignUpdated","summary":"Campagne modifiée","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"campaign.qr_attached":{"post":{"operationId":"eventCampaignQrAttached","summary":"QR codes existants associés à une campagne","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"campaign.qr_detached":{"post":{"operationId":"eventCampaignQrDetached","summary":"QR code retiré d’une campagne (le QR n’est pas supprimé)","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}},"alert.triggered":{"post":{"operationId":"eventAlertTriggered","summary":"Alerte de performance déclenchée","description":"En-tête `InvexQR-Signature: t=<horodatage>,v1=<HMAC-SHA256(secret, t + \".\" + corps)>`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"api_version":{"type":"string"},"organization_id":{"type":"integer"},"data":{"type":"object"}}}}}},"responses":{"2XX":{"description":"Reçu (toute réponse 2xx). Sinon nouvelles tentatives : 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"iqr_live_<préfixe>_<secret>","description":"Permissions possibles : qr:read, qr:write, campaigns:read, campaigns:write, stats:read, webhooks:manage."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Identifiant unique de l’opération (1 à 100 caractères). Une requête rejouée avec la même clé et le même corps renvoie la réponse initiale (en-tête Idempotent-Replayed) pendant 24 h.","schema":{"type":"string","maxLength":100}}},"responses":{"E400":{"description":"Requête invalide (JSON mal formé, clé d’idempotence invalide).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E401":{"description":"Clé absente, invalide, révoquée ou expirée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E403":{"description":"Formule, permission ou quota insuffisant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E404":{"description":"Ressource introuvable (ou appartenant à une autre organisation).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E409":{"description":"Conflit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E422":{"description":"Données invalides : détail par champ dans error.fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E429":{"description":"Débit par minute ou quota mensuel dépassé (voir Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"type":"string","description":"Code stable, à utiliser dans votre logique."},"message":{"type":"string","description":"Message en français."},"fields":{"type":"object","additionalProperties":{"type":"string"}},"request_id":{"type":"string"}}}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}},"CursorMeta":{"type":"object","properties":{"has_more":{"type":"boolean"},"next_after_id":{"type":["integer","null"]},"limit":{"type":"integer"},"raw_retention_days":{"type":"integer"}}},"Me":{"type":"object","properties":{"organization":{"type":"object"},"api_key":{"type":"object"},"usage":{"type":"object","properties":{"month":{"type":"string"},"requests":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"rate_limit":{"type":"object"},"quotas":{"type":"object"}}},"Domain":{"type":"object","properties":{"id":{"type":"integer"},"hostname":{"type":"string"},"status":{"type":"string","enum":["pending_dns","dns_ok","active","disabled"]},"is_default":{"type":"boolean"},"base_url":{"type":"string"},"activated_at":{"type":["string","null"],"format":"date-time"}}},"QrCode":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["static","redirect","multilink","file","coupon"]},"mode":{"type":"string","enum":["static","dynamic"]},"status":{"type":"string","enum":["active","paused","suspended"]},"format":{"type":"string"},"data_type":{"type":"string"},"content":{"type":"string","description":"Texte encodé dans le QR (adresse courte pour un QR dynamique)."},"short_url":{"type":["string","null"]},"share_url":{"type":["string","null"],"description":"Lien à partager (MultiLink), compté comme visite et non comme scan."},"destination":{"type":["string","null"]},"domain":{"type":["string","null"]},"campaign_id":{"type":["integer","null"],"description":"Campagne dont ce QR est un support (destination gérée par elle)."},"campaign_ids":{"type":"array","items":{"type":"integer"},"description":"Campagnes auxquelles le QR est associé en ce moment (supports et QR existants)."},"channel":{"type":["string","null"]},"support_cost":{"type":["integer","null"]},"batch_id":{"type":["integer","null"]},"expires_at":{"type":["string","null"],"format":"date-time"},"expired_url":{"type":["string","null"]},"style":{"type":"object"},"has_logo":{"type":"boolean"},"app_links":{"type":["object","null"],"description":"Lien d’application (data_type « app ») : boutique ouverte selon le téléphone.","properties":{"ios":{"type":["string","null"]},"android":{"type":["string","null"]}}},"image_url":{"type":["string","null"]},"dashboard_url":{"type":"string"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"QrCreate":{"type":"object","properties":{"kind":{"type":"string","enum":["redirect","static"],"default":"redirect"},"name":{"type":"string","maxLength":120},"destination":{"type":"string","description":"Obligatoire pour kind=redirect (https:// ajouté si absent)."},"content":{"type":"string","maxLength":4000,"description":"Obligatoire pour kind=static (ex. WIFI:T:WPA;S:Boutique;P:motdepasse;;)."},"data_type":{"type":"string","description":"Type de contenu statique (texte libre, informatif)."},"style":{"type":"object","description":"Style graphique (voir la page Développeurs).","properties":{"fg":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"fg2":{"type":"string","description":"Seconde couleur (dégradé)."},"gradient":{"type":"string","enum":["linear","radial"]},"bg":{"type":"string"},"transparent":{"type":"boolean"},"module":{"type":"string","enum":["square","rounded","dots","diamond","vertical"]},"eye":{"type":"string","enum":["square","rounded","circle"]},"eyeColor":{"type":"string"},"ecc":{"type":"string","enum":["L","M","Q","H"]},"margin":{"type":"integer","minimum":0,"maximum":10},"logo":{"type":"string","description":"Image data:image/png;base64,… (300 Ko max)."},"logoSize":{"type":"number","minimum":0.1,"maximum":0.3},"frame":{"type":"string","enum":["none","bottom","box"]},"frameText":{"type":"string","maxLength":40},"frameColor":{"type":"string"},"frameTextColor":{"type":"string"}}},"domain":{"type":"string","description":"Nom d’hôte d’un domaine personnalisé actif, ou « platform ». Défaut : domaine par défaut de l’organisation."},"expires_at":{"type":["string","null"],"format":"date-time"},"expired_url":{"type":"string"},"app_links":{"type":"object","description":"kind=redirect : iPhone vers « ios » (App Store), Android vers « android » (Google Play), autres appareils vers « destination ».","properties":{"ios":{"type":"string"},"android":{"type":"string"}}},"campaign_id":{"type":"integer"},"channel":{"type":"string","enum":["affiche","flyer","depliant","carte_visite","emballage","etiquette","facture","ticket_caisse","vitrine","panneau","presse","television","radio","evenement","salon","sms","email","reseaux_sociaux","publicite_numerique","catalogue","menu","support_commercial"]},"support_cost":{"type":"integer"}}},"QrUpdate":{"type":"object","properties":{"name":{"type":"string"},"destination":{"type":"string"},"status":{"type":"string","enum":["active","paused"]},"style":{"type":"object","description":"Style graphique (voir la page Développeurs).","properties":{"fg":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"fg2":{"type":"string","description":"Seconde couleur (dégradé)."},"gradient":{"type":"string","enum":["linear","radial"]},"bg":{"type":"string"},"transparent":{"type":"boolean"},"module":{"type":"string","enum":["square","rounded","dots","diamond","vertical"]},"eye":{"type":"string","enum":["square","rounded","circle"]},"eyeColor":{"type":"string"},"ecc":{"type":"string","enum":["L","M","Q","H"]},"margin":{"type":"integer","minimum":0,"maximum":10},"logo":{"type":"string","description":"Image data:image/png;base64,… (300 Ko max)."},"logoSize":{"type":"number","minimum":0.1,"maximum":0.3},"frame":{"type":"string","enum":["none","bottom","box"]},"frameText":{"type":"string","maxLength":40},"frameColor":{"type":"string"},"frameTextColor":{"type":"string"}}},"expires_at":{"type":["string","null"],"format":"date-time"},"expired_url":{"type":"string"},"app_links":{"type":"object","properties":{"ios":{"type":"string"},"android":{"type":"string"}}}}},"BulkCreate":{"type":"object","required":["items"],"properties":{"mode":{"type":"string","enum":["dynamic","static"],"default":"dynamic"},"name":{"type":"string","maxLength":120},"campaign_id":{"type":"integer"},"domain":{"type":"string"},"items":{"type":"array","minItems":1,"items":{"type":"object","properties":{"name":{"type":"string"},"destination":{"type":"string"},"content":{"type":"string"},"channel":{"type":"string","enum":["affiche","flyer","depliant","carte_visite","emballage","etiquette","facture","ticket_caisse","vitrine","panneau","presse","television","radio","evenement","salon","sms","email","reseaux_sociaux","publicite_numerique","catalogue","menu","support_commercial"]}}}}}},"HistoryEntry":{"type":"object","properties":{"link_id":{"type":["integer","null"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"changed_at":{"type":["string","null"],"format":"date-time"}}},"Link":{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string"},"label":{"type":"string"},"value":{"type":"string"},"url":{"type":"string"},"active":{"type":"boolean"},"position":{"type":"integer"},"click_url":{"type":"string"}}},"LinkInput":{"type":"object","required":["type","value"],"properties":{"id":{"type":"integer","description":"Lien existant à conserver."},"type":{"type":"string"},"value":{"type":"string"},"label":{"type":"string","maxLength":80},"active":{"type":"boolean","default":true}}},"MultilinkPage":{"allOf":[{"$ref":"#/components/schemas/QrCode"},{"type":"object","properties":{"page":{"type":"object","properties":{"title":{"type":"string"},"bio":{"type":["string","null"]},"theme":{"type":"string"},"colors":{"type":"object"},"logo_url":{"type":["string","null"]}}},"links":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}}]},"MultilinkCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":120},"bio":{"type":"string","maxLength":400},"theme":{"type":"string"},"sector":{"type":"string"},"domain":{"type":"string"},"links":{"type":"array","items":{"$ref":"#/components/schemas/LinkInput"}}}},"MultilinkUpdate":{"type":"object","properties":{"title":{"type":"string"},"bio":{"type":"string"},"theme":{"type":"string"},"accent":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"links":{"type":"array","items":{"$ref":"#/components/schemas/LinkInput"}}}},"Campaign":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":["string","null"]},"objective":{"type":"string","enum":["notoriete","trafic","prospects","ventes","fidelisation","evenement"]},"owner_name":{"type":["string","null"]},"status":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":["string","null"],"format":"date"},"budget":{"type":["integer","null"]},"currency":{"type":"string"},"audience":{"type":["string","null"]},"destination":{"type":"string"},"utm_enabled":{"type":"boolean"},"conversion_goal":{"type":["string","null"]},"conversion_tracking":{"type":"boolean"},"conversion_url":{"type":"string","description":"Adresse de conversion (pixel ou appel serveur, sans clé d’API)."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"CampaignDetail":{"allOf":[{"$ref":"#/components/schemas/Campaign"},{"type":"object","properties":{"period":{"type":"object"},"totals":{"type":"object"},"supports":{"type":"array","items":{"type":"object"},"description":"QR créés par la campagne (compatibilité)."},"qr_codes":{"type":"array","items":{"type":"object"},"description":"Tous les QR de la campagne (supports et QR existants associés) : scans attribués sur la période, `global_scans` (total du QR, toutes campagnes), association."},"inactive_7_days":{"type":"boolean"}}}]},"CampaignQr":{"type":"object","properties":{"campaign_id":{"type":"integer"},"qr_id":{"type":"integer"},"origin":{"type":"string","enum":["support","existing"]},"current":{"type":"boolean"},"counted_from":{"type":"string","format":"date","description":"Premier jour (local) dont les scans sont attribués."},"counted_until":{"type":["string","null"],"format":"date","description":"Premier jour (local) non attribué ; null tant que l’association est en cours."},"cost":{"type":["integer","null"]},"pass_token":{"type":"boolean"},"attached_at":{"type":["string","null"],"format":"date-time"},"detached_at":{"type":["string","null"],"format":"date-time"}}},"AttachResult":{"type":"object","properties":{"attached":{"type":"array","items":{"type":"integer"}},"already_attached":{"type":"array","items":{"type":"integer"}},"refused":{"type":"array","items":{"type":"object","properties":{"qr_id":{"type":"integer"},"reason":{"type":"string"}}}},"not_found":{"type":"array","items":{"type":"integer"}},"counted_from":{"type":"string","format":"date"}}},"CampaignInput":{"type":"object","required":["name","destination"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"objective":{"type":"string","enum":["notoriete","trafic","prospects","ventes","fidelisation","evenement"]},"owner_name":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"budget":{"type":"integer"},"audience":{"type":"string"},"destination":{"type":"string"},"utm_enabled":{"type":"boolean","default":true},"conversion_goal":{"type":"string"},"conversion_tracking":{"type":"boolean","default":false}}},"CampaignUpdate":{"allOf":[{"type":"object","properties":{"status":{"type":"string","enum":["active","paused","archived"]}}},{"type":"object","properties":{"name":{"type":"string"},"destination":{"type":"string"},"budget":{"type":"integer"},"end_date":{"type":"string","format":"date"}}}]},"Conversion":{"type":"object","properties":{"id":{"type":"integer"},"campaign_id":{"type":"integer"},"qr_id":{"type":["integer","null"]},"source":{"type":"string","enum":["tracked","manual"]},"quantity":{"type":"integer"},"value":{"type":"integer"},"currency":{"type":"string"},"occurred_at":{"type":["string","null"],"format":"date-time"},"note":{"type":["string","null"]}}},"TrackedConversion":{"type":"object","properties":{"status":{"type":"string","enum":["recorded","duplicate"]},"conversion":{"$ref":"#/components/schemas/Conversion"}}},"Stats":{"type":"object","properties":{"period":{"type":"object"},"totals":{"type":"object","properties":{"scans":{"type":"integer"},"unique_visitors":{"type":"integer"},"visits":{"type":"integer"},"clicks":{"type":"integer"},"scans_raw":{"type":"integer"},"excluded":{"type":"object"}}},"series":{"type":"array","items":{"type":"object"}},"breakdowns":{"type":"object"},"links":{"type":"array","items":{"type":"object"}},"per_qr":{"type":"array","items":{"type":"object"}},"counting_method":{"type":"string"},"generated_at":{"type":["string","null"],"format":"date-time"}}},"Event":{"type":"object","properties":{"id":{"type":"integer"},"qr_id":{"type":"integer"},"link_id":{"type":["integer","null"]},"variant_id":{"type":["integer","null"]},"type":{"type":"string","enum":["scan","visit","click"]},"status":{"type":"string"},"status_reason":{"type":["string","null"]},"occurred_at":{"type":["string","null"],"format":"date-time"},"device":{"type":"string"},"os":{"type":"string"},"browser":{"type":"string"},"country":{"type":["string","null"]},"city":{"type":["string","null"],"description":"Ville estimée (jamais la position du téléphone)."},"geo_status":{"type":["string","null"],"enum":["city","country","unknown",null]},"referrer_host":{"type":["string","null"]}}},"Webhook":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string","enum":["scan.recorded","click.recorded","conversion.recorded","coupon.issued","coupon.redeemed","qr.created","qr.updated","qr.deleted","batch.created","campaign.created","campaign.updated","campaign.qr_attached","campaign.qr_detached","alert.triggered"]}},"status":{"type":"string","enum":["active","disabled"]},"disabled_reason":{"type":["string","null"]},"consecutive_failures":{"type":"integer"},"last_success_at":{"type":["string","null"],"format":"date-time"},"last_failure_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"deliveries_7d":{"type":"object"}}},"WebhookWithSecret":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"whsec_… — affiché une seule fois."}}}]},"Delivery":{"type":"object","properties":{"id":{"type":"integer"},"event_id":{"type":"string"},"event_type":{"type":"string"},"status":{"type":"string","enum":["pending","success","failed"]},"attempts":{"type":"integer"},"next_attempt_at":{"type":["string","null"],"format":"date-time"},"last_status_code":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"response_excerpt":{"type":["string","null"]},"duration_ms":{"type":["integer","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time"},"payload":{"type":"object"}}}}}}