{"openapi":"3.1.0","info":{"title":"API ScanETF","version":"1.0.0","description":"Accès programmatique à vos portefeuilles ETF ScanETF et à l’ensemble des fonctionnalités que propose le Rayon X, mais en version API.","contact":{"name":"ScanETF","url":"https://scanetf.com/mon-espace/api"}},"servers":[{"url":"https://scanetf.com/api/v1","description":"Production"}],"tags":[{"name":"Portefeuilles"},{"name":"Analyse"},{"name":"Performance"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Clé API, à générer sur https://scanetf.com/mon-espace/api. Elle commence par `sk-scanetf-api-` et n'est affichée qu'une fois."}},"schemas":{"Holding":{"type":"object","properties":{"isin":{"type":"string","minLength":1,"description":"ISIN de l’ETF"},"poids_pct":{"type":"number","minimum":0,"maximum":100,"description":"Mode pourcentage : poids dans le portefeuille, en %. Exclusif de `parts`."},"parts":{"type":"number","exclusiveMinimum":0,"description":"Mode parts : nombre de parts détenues (fractions acceptées). Le poids est calculé au dernier cours connu. Exclusif de `poids_pct`."}},"required":["isin"],"additionalProperties":false,"oneOf":[{"type":"object","title":"Mode pourcentage","required":["poids_pct"],"not":{"required":["parts"]}},{"type":"object","title":"Mode parts","required":["parts"],"not":{"required":["poids_pct"]}}]},"CreatePortfolio":{"type":"object","properties":{"nom":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du portefeuille"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"enveloppe":{"anyOf":[{"type":"string","enum":["PEA","CTO","PEA-PME","Assurance Vie","PER"]},{"type":"null"}],"description":"Enveloppe fiscale"},"courtier":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"description":"Texte libre, ex. \"Boursobank\", \"Trade Republic\""},"tag":{"anyOf":[{"type":"string","enum":["Principal","Optimisé","Test","Archivé"]},{"type":"null"}],"description":"Étiquette"},"lignes":{"type":"array","items":{"type":"object","properties":{"isin":{"type":"string","minLength":1,"description":"ISIN de l’ETF"},"poids_pct":{"type":"number","minimum":0,"maximum":100,"description":"Mode pourcentage : poids dans le portefeuille, en %. Exclusif de `parts`."},"parts":{"type":"number","exclusiveMinimum":0,"description":"Mode parts : nombre de parts détenues (fractions acceptées). Le poids est calculé au dernier cours connu. Exclusif de `poids_pct`."}},"required":["isin"],"additionalProperties":false,"oneOf":[{"type":"object","title":"Mode pourcentage","required":["poids_pct"],"not":{"required":["parts"]}},{"type":"object","title":"Mode parts","required":["parts"],"not":{"required":["poids_pct"]}}]},"minItems":1,"maxItems":50,"description":"Composition complète. En mode pourcentage, la somme des poids doit valoir 100 (tolérance 0,05). En mode parts, aucune contrainte de somme."}},"required":["nom"],"additionalProperties":false},"UpdatePortfolio":{"type":"object","properties":{"nom":{"type":"string","minLength":1,"maxLength":100,"description":"Nom du portefeuille"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"enveloppe":{"anyOf":[{"type":"string","enum":["PEA","CTO","PEA-PME","Assurance Vie","PER"]},{"type":"null"}],"description":"Enveloppe fiscale"},"courtier":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"description":"Texte libre, ex. \"Boursobank\", \"Trade Republic\""},"tag":{"anyOf":[{"type":"string","enum":["Principal","Optimisé","Test","Archivé"]},{"type":"null"}],"description":"Étiquette"},"lignes":{"type":"array","items":{"type":"object","properties":{"isin":{"type":"string","minLength":1,"description":"ISIN de l’ETF"},"poids_pct":{"type":"number","minimum":0,"maximum":100,"description":"Mode pourcentage : poids dans le portefeuille, en %. Exclusif de `parts`."},"parts":{"type":"number","exclusiveMinimum":0,"description":"Mode parts : nombre de parts détenues (fractions acceptées). Le poids est calculé au dernier cours connu. Exclusif de `poids_pct`."}},"required":["isin"],"additionalProperties":false,"oneOf":[{"type":"object","title":"Mode pourcentage","required":["poids_pct"],"not":{"required":["parts"]}},{"type":"object","title":"Mode parts","required":["parts"],"not":{"required":["poids_pct"]}}]},"minItems":1,"maxItems":50,"description":"Si présent, REMPLACE INTÉGRALEMENT la composition. Omettre pour ne modifier que les informations du portefeuille."}},"additionalProperties":false}}},"paths":{"/portfolios":{"get":{"operationId":"listPortfolios","tags":["Portefeuilles"],"summary":"Lister vos portefeuilles","description":"Nom, enveloppe fiscale, courtier, étiquette et nombre de lignes. Point de départ : les autres endpoints attendent un identifiant renvoyé ici.","responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}},"post":{"operationId":"createPortfolio","tags":["Portefeuilles"],"summary":"Créer un portefeuille","description":"Crée un portefeuille, avec sa composition si elle est fournie — en pourcentage (la somme des poids doit valoir 100) ou en nombre de parts (valorisées au dernier cours), comme dans l’éditeur du site.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortfolio"},"examples":{"Mode pourcentage":{"summary":"Composition en % (total 100)","value":{"nom":"Mon PEA","enveloppe":"PEA","courtier":"Boursobank","tag":"Principal","lignes":[{"isin":"IE00B4L5Y983","poids_pct":60},{"isin":"IE00B5BMR087","poids_pct":40}]}},"Mode parts":{"summary":"Composition en nombre de parts, valorisées au dernier cours","value":{"nom":"Mon CTO","enveloppe":"CTO","lignes":[{"isin":"IE00B4L5Y983","parts":12},{"isin":"IE00B5BMR087","parts":3.5}]}},"Sans composition":{"summary":"Portefeuille vide, à composer plus tard","value":{"nom":"À construire"}}}}}},"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getPortfolio","tags":["Portefeuilles"],"summary":"Composition détaillée","description":"Chaque ligne avec son ISIN, son poids, le montant investi, les parts, les frais et l’éligibilité PEA.","responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}},"patch":{"operationId":"updatePortfolio","tags":["Portefeuilles"],"summary":"Modifier un portefeuille","description":"Modifie les informations (nom, description, enveloppe, courtier, étiquette) et/ou la composition. Seuls les champs envoyés changent ; `lignes`, si présent, remplace intégralement la composition. La composition est refusée si le portefeuille comporte des transactions dans le suivi DCA, pour ne pas fausser les positions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePortfolio"},"examples":{"Informations seules":{"summary":"Renommer et étiqueter, sans toucher à la composition","value":{"nom":"PEA principal","tag":"Principal"}},"Composition seule":{"summary":"Remplacer toute la composition","value":{"lignes":[{"isin":"IE00B4L5Y983","poids_pct":100}]}},"Les deux":{"summary":"Informations et composition en un seul appel","value":{"enveloppe":"CTO","lignes":[{"isin":"IE00B4L5Y983","parts":12},{"isin":"IE00B5BMR087","parts":3.5}]}}}}}},"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}},"delete":{"operationId":"deletePortfolio","tags":["Portefeuilles"],"summary":"Supprimer définitivement","description":"Emporte les lignes, les transactions et le plan DCA. Irréversible : le nom exact est exigé en confirmation.","parameters":[{"name":"nom","in":"query","required":true,"description":"Nom exact du portefeuille.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}/analysis":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getAnalysis","tags":["Analyse"],"summary":"Analyse Rayon X","description":"Frais pondérés, éligibilité PEA, disponibilité courtiers, rendement de dividende pondéré avec les ETF qui le produisent, exposition complète par continent, pays et secteur, et le détail ligne à ligne (frais, coût annuel, capitalisant ou distribuant avec son rendement, réplication).","responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}/positions":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getPositions","tags":["Analyse"],"summary":"Positions consolidées","description":"Ce que vous détenez réellement, toutes lignes confondues : chaque entreprise avec son poids dans le portefeuille et les ETF par lesquels vous la détenez. Les positions détenues via plusieurs ETF sont marquées `en_doublon`. Liste paginée, par poids décroissant ; les positions sous 0,01 % du portefeuille ne sont pas listées, mais restent comptées dans `chevauchement_pct`.","parameters":[{"name":"limite","in":"query","required":false,"description":"Taille de page : 25 par défaut, 100 au maximum.","schema":{"type":"integer","default":25,"maximum":100,"minimum":1}},{"name":"offset","in":"query","required":false,"description":"Rang de la première position renvoyée, 0 par défaut. Reprendre l’`offset_suivant` de la réponse ; il vaut `null` quand tout a été lu.","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}/backtest":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getBacktest","tags":["Performance"],"summary":"Performance historique","description":"Performance totale, rendement et volatilité annualisés, ratio de Sharpe, perte maximale, rendements annuels et mensuels, à allocation constante. La série ne commence qu’au premier jour où toutes les lignes cotent : si une ligne récente tronque la fenêtre demandée, `tronquee_par_historique` le signale et `ligne_limitante` la nomme.","parameters":[{"name":"periode","in":"query","required":false,"description":"Fenêtre nommée : 1M, 3M, 6M, 1Y, 3Y, 5Y, Max. Par défaut 1Y, comme la page Comparer du site.","schema":{"type":"string","enum":["1M","3M","6M","1Y","3Y","5Y","Max"],"default":"1Y"}},{"name":"date_debut","in":"query","required":false,"description":"Date de début AAAA-MM-JJ. Prioritaire sur periode.","schema":{"type":"string","format":"date"}},{"name":"date_fin","in":"query","required":false,"description":"Date de fin AAAA-MM-JJ. Aujourd’hui par défaut.","schema":{"type":"string","format":"date"}},{"name":"exclure","in":"query","required":false,"description":"ISIN à retirer du calcul, séparés par des virgules.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}/correlation":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCorrelation","tags":["Performance"],"summary":"Corrélation entre les lignes","description":"Matrice de corrélation, paires les plus corrélées et groupes de lignes quasi interchangeables (≥ 0,8). Deux ETF très corrélés n’apportent pas de diversification l’un par rapport à l’autre.","parameters":[{"name":"periode","in":"query","required":false,"description":"Fenêtre nommée : 1M, 3M, 6M, 1Y, 3Y, 5Y, Max. Par défaut 1Y, comme la page Comparer du site.","schema":{"type":"string","enum":["1M","3M","6M","1Y","3Y","5Y","Max"],"default":"1Y"}},{"name":"date_debut","in":"query","required":false,"description":"Date de début AAAA-MM-JJ. Prioritaire sur periode.","schema":{"type":"string","format":"date"}},{"name":"date_fin","in":"query","required":false,"description":"Date de fin AAAA-MM-JJ. Aujourd’hui par défaut.","schema":{"type":"string","format":"date"}},{"name":"exclure","in":"query","required":false,"description":"ISIN à retirer du calcul, séparés par des virgules.","schema":{"type":"string"}}],"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}},"/portfolios/{id}/prices":{"parameters":[{"name":"id","in":"path","required":true,"description":"Identifiant du portefeuille, renvoyé par GET /portfolios.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getPrices","tags":["Performance"],"summary":"Série de valorisation","description":"Valeur du portefeuille jour par jour, base 100 au premier point commun aux lignes retenues. De quoi tracer la courbe dans votre outil. La série est renvoyée entière — sur cinq ans, comptez ~1 300 points et ~50 Ko ; `granularite` la résume à la semaine ou au mois.","parameters":[{"name":"periode","in":"query","required":false,"description":"Fenêtre nommée : 1M, 3M, 6M, 1Y, 3Y, 5Y, Max. Par défaut 1Y, comme la page Comparer du site.","schema":{"type":"string","enum":["1M","3M","6M","1Y","3Y","5Y","Max"],"default":"1Y"}},{"name":"date_debut","in":"query","required":false,"description":"Date de début AAAA-MM-JJ. Prioritaire sur periode.","schema":{"type":"string","format":"date"}},{"name":"date_fin","in":"query","required":false,"description":"Date de fin AAAA-MM-JJ. Aujourd’hui par défaut.","schema":{"type":"string","format":"date"}},{"name":"exclure","in":"query","required":false,"description":"ISIN à retirer du calcul, séparés par des virgules.","schema":{"type":"string"}},{"name":"granularite","in":"query","required":false,"description":"`jour` (par défaut), `semaine` ou `mois` : un point par jour de cotation, ou le dernier point de chaque semaine ou mois. `nb_points` et `nb_jours_cotation` disent ce qui a été renvoyé et ce qui a été résumé.","schema":{"type":"string","enum":["jour","semaine","mois"],"default":"jour"}}],"responses":{"200":{"description":"Succès. Le champ `summary` résume le résultat en français.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"}},"additionalProperties":true}}}},"400":{"description":"Règle métier non respectée. Le champ `message` l’explique en clair."},"401":{"description":"Clé absente, invalide, révoquée, ou non valable sur cette API."},"402":{"description":"Quota gratuit épuisé (30 appels). ScanETF Pro le rend illimité."},"429":{"description":"Garde-fou anti-abus : 2 000 appels par jour et par clé."}}}}}}