{"openapi":"3.1.0","info":{"title":"Subtraq API","version":"2026-08-25","description":"Suivi de liens avec attribution : du clic au prospect, puis à la vente.\n\nTrois règles à connaître avant d'écrire la première ligne :\n· tous les MONTANTS sont en CENTIMES (43 € = 4300) ;\n· les listes se parcourent au CURSEUR (`nextCursor`), jamais par numéro de page ;\n· les écritures acceptent un en-tête `Idempotency-Key` — le rejeu rend la réponse d'origine.","contact":{"email":"contact@agenceguddelmoni.com"}},"servers":[{"url":"https://subtraq.co"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API de l'agence (`stq_sk_…`), créée depuis Réglages → Clés API."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Code stable, sur lequel on peut brancher du code."},"message":{"type":"string","description":"Phrase lisible par un humain."}}}}}}},"paths":{"/api/v1/spaces":{"get":{"operationId":"list_spaces","summary":"Liste les espaces clients de l'agence. Un espace = un client final, une marque ou un projet ; tout le reste (liens, clics, ventes) vit dedans.","tags":["spaces"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:read","parameters":[{"name":"limit","in":"query","required":false,"description":"1 à 100, 50 par défaut.","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Curseur renvoyé par l'appel précédent (`nextCursor`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}},"post":{"operationId":"create_space","summary":"Crée un espace client. Refusé avec le code `space_limit_reached` si la formule est pleine.","tags":["spaces"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:write","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Nom affiché, par exemple « Maison Lartigue »."},"slug":{"type":"string","description":"Raccourci dans les URL. Déduit du nom s'il est omis."}},"required":["name"]}}}},"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}}},"/api/v1/links":{"get":{"operationId":"list_links","summary":"Liste les liens. Un lien PARENT porte la destination ; un PLACEMENT (sublink) porte les UTM d'une publication précise et hérite du reste.","tags":["links"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:read","parameters":[{"name":"space","in":"query","required":false,"description":"Raccourci de l'espace. Omis = toute l'agence.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"active (défaut), archived, ou all.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1 à 100.","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Curseur de la page suivante.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}},"post":{"operationId":"create_link","summary":"Crée un lien court. Sans `parentId`, c'est un lien parent et `destination` est obligatoire. Avec `parentId`, c'est un placement : il hérite de la destination du parent et porte ses propres UTM. Renvoie `shortUrl`, prêt à publier.","tags":["links"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:write","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"space":{"type":"string","description":"Raccourci de l'espace client."},"destination":{"type":"string","description":"URL d'arrivée. Obligatoire pour un parent, interdite pour un placement."},"parentId":{"type":"string","description":"Identifiant du lien parent, pour créer un placement."},"slug":{"type":"string","description":"Raccourci souhaité. Tiré au sort s'il est omis."},"label":{"type":"string","description":"Nom du placement, par exemple « Pub Meta — mars »."},"utmSource":{"type":"string","description":"utm_source du placement."},"utmMedium":{"type":"string","description":"utm_medium du placement."},"utmCampaign":{"type":"string","description":"utm_campaign du placement."},"utmTerm":{"type":"string","description":"utm_term du placement."},"utmContent":{"type":"string","description":"utm_content du placement."}},"required":["space"]}}}},"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}}},"/api/v1/links/{id}":{"get":{"operationId":"get_link","summary":"Détaille un lien : sa destination FINALE (UTM comprises, telle que la reçoit le site d'arrivée) et ses clics sur 30 jours.","tags":["links"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:read","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du lien.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}},"patch":{"operationId":"update_link","summary":"Modifie la destination ou le libellé d'un lien, ou l'archive. Un lien n'est JAMAIS supprimé : archivé, il cesse de rediriger mais son historique reste.","tags":["links"],"security":[{"bearerAuth":[]}],"x-required-scope":"links:write","parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du lien.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"destination":{"type":"string","description":"Nouvelle URL d'arrivée (liens parents seulement)."},"label":{"type":"string","description":"Nouveau libellé."},"archived":{"type":"boolean","description":"true pour archiver, false pour réactiver."}},"required":[]}}}},"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}}},"/api/v1/analytics":{"get":{"operationId":"get_analytics","summary":"Les chiffres d'un espace : clics, prospects, ventes, revenu ATTRIBUÉ et revenu NON attribué, plus le détail placement par placement. Montants en CENTIMES. Subtraq ne convertit pas les devises : si `mixedCurrencies` est vrai, les totaux ne concernent que `currency`. `model` choisit la lecture de l'attribution ; la réponse rappelle toujours lequel a servi.","tags":["analytics"],"security":[{"bearerAuth":[]}],"x-required-scope":"analytics:read","parameters":[{"name":"space","in":"query","required":true,"description":"Raccourci de l'espace client.","schema":{"type":"string"}},{"name":"period","in":"query","required":false,"description":"7d, 30d (défaut), 90d ou all.","schema":{"type":"string"}},{"name":"model","in":"query","required":false,"description":"Modèle d'attribution : `first` (premier clic, défaut et référence du produit), `last` (dernier clic avant la conversion) ou `linear` (partage entre tous les placements cliqués, revenu réparti au centime près). Dans les trois cas, attribué + non attribué = revenu total.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}}},"/api/v1/sales":{"post":{"operationId":"track_sale","summary":"Enregistre une VENTE et la rattache au placement d'origine de la personne. Montant en CENTIMES. `invoiceId` rend l'appel idempotent : le rejouer ne facture jamais deux fois. Une vente ne peut PAS être envoyée depuis un navigateur.","tags":["sales"],"security":[{"bearerAuth":[]}],"x-required-scope":"events:write","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"space":{"type":"string","description":"Raccourci de l'espace client."},"email":{"type":"string","description":"Email de la personne — le plus simple pour la retrouver."},"externalId":{"type":"string","description":"Son identifiant dans votre système, si vous en avez un."},"clickId":{"type":"string","description":"Identifiant de clic, si vous l'avez conservé."},"amount":{"type":"integer","description":"Montant en CENTIMES. 43 € s'écrit 4300."},"currency":{"type":"string","description":"Code à trois lettres. USD par défaut."},"invoiceId":{"type":"string","description":"Votre numéro de facture — c'est lui qui garantit l'idempotence."}},"required":["space","amount","invoiceId"]}}}},"responses":{"200":{"description":"Succès."},"201":{"description":"Créé."},"400":{"description":"Paramètres invalides.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clé absente ou révoquée."},"402":{"description":"Quota de la formule atteint."},"403":{"description":"La clé n'a pas le droit demandé."},"404":{"description":"Introuvable dans cette agence."},"409":{"description":"Conflit — un raccourci déjà pris, par exemple."}}}}}}