Exploiter les API dans Power BI : Authentification JWT et Pagination avec Power Query
Table des matières
- Introduction
- Exploration de l’API DummyJSON
- Configuration des paramètres et des fonctions Power Query
- Utilisation pratique
- Conclusion
Introduction
Si vous utilisez Power BI, vous avez probablement déjà eu besoin d’importer des données depuis une API REST. Et vous savez quoi ? Ce n’est pas toujours une partie de plaisir ! Entre l’authentification, les en-têtes HTTP, la pagination… on peut vite se perdre.
Dans cet article, je vais vous montrer concrètement comment importer des données d’une API dans Power BI en utilisant Power Query. On va voir ensemble comment gérer l’authentification JWT et la pagination, le tout avec un exemple pratique basé sur l’API DummyJSON . C’est une API gratuite et simple qui simule parfaitement les cas d’usage réels.
Avant de commencer, j’imagine que vous :
- Connaissez déjà les bases de Power BI Desktop
- Avez déjà ouvert l’éditeur Power Query au moins une fois
- Comprenez les concepts de base des API REST (GET, POST, en-têtes, JSON)
Si c’est le cas, parfait ! On peut y aller.
Exploration de l’API DummyJSON
Avant de plonger dans le code, prenons quelques minutes pour comprendre comment fonctionne l’API qu’on va utiliser. DummyJSON est une ressource géniale qui fournit des données fictives (produits, utilisateurs, articles, etc.) et simule les mécanismes typiques des vraies API : authentification, pagination, etc.
Voici le processus général pour récupérer des données depuis une API protégée :
Étape 1 : L’authentification
Envoi des identifiants
La première chose à faire, c’est s’authentifier. On envoie une requête POST à l’endpoint /auth/login avec nos identifiants au format JSON :
{ "username": "emilys", "password": "emilyspass"}Réception des jetons
En retour, l’API nous donne un objet JSON avec deux jetons importants :
accessToken: c’est notre sésame pour accéder aux données protégéesrefreshToken: il servira à renouveler l’access token quand il expirera
Étape 2 : Récupération des données
Envoi de la requête avec le jeton
Maintenant qu’on a notre accessToken, on peut demander les données ! On envoie une requête GET à /auth/products en incluant le jeton dans l’en-tête Authorization comme ceci : Bearer votre_access_token.
Réception des données
L’API nous renvoie un objet JSON contenant :
- La liste des produits dans le champ
products - Des infos sur la pagination :
total(nombre total d’éléments),skip(combien on en a sauté),limit(combien on en récupère par page)
Voici un exemple de réponse simplifiée :
{ "products": [ { "id": 1, "title": "iPhone 13 Pro", "description": "Latest iPhone model with advanced features", "category": "Electronics", "price": 999.99, "discountPercentage": 10.5, "rating": 4.7, "stock": 50, "brand": "Apple", ... }, ... ], "total": 80, "skip": 0, "limit": 20}Vous voyez le principe ? Maintenant qu’on comprend le fonctionnement, passons à l’implémentation dans Power Query !
Configuration des paramètres et des fonctions Power Query
C’est parti pour le code ! On va construire notre solution étape par étape en créant des paramètres et des fonctions réutilisables. L’idée, c’est de faire les choses proprement dès le départ pour faciliter la maintenance et les évolutions futures.
1. Configuration des paramètres globaux Power Query
Commençons par créer des paramètres qui vont centraliser toutes nos configurations. Comme ça, si vous devez changer l’URL de l’API ou vos identifiants, vous n’aurez qu’un seul endroit à modifier !
Comment faire :
-
Dans l’Éditeur Power Query, allez dans l’onglet
Accueil -
Cliquez sur
Gérer les paramètres>Nouveau paramètre -
Créez les paramètres suivants :
API_BASE_URL:Description: L’URL de base de l’APIType: TexteValeur: https://dummyjson.com
API_LOGIN_USERNAME:Description: Votre nom d’utilisateurType: TexteValeur: emilys
API_LOGIN_PASSWORD:Description: Votre mot de passeType: TexteValeur: emilyspass
API_PAGINATION_SKIP:Description: Point de départ pour la paginationType: Nombre décimalValeur: 0
API_PAGINATION_LIMIT:Description: Nombre d’éléments par pageType: Nombre décimalValeur: 20
2. Fonctions utilitaires pour les requêtes HTTP
Maintenant, on va créer quatre fonctions “briques de base” qui vont nous simplifier la vie pour tous nos appels API. Ces fonctions vont gérer les en-têtes, les paramètres de requête, et les appels HTTP eux-mêmes.
Comment créer une fonction :
- Dans l’Éditeur Power Query, onglet
Accueil - Cliquez sur
Nouvelle source>Requête vide - Ouvrez l’éditeur avancé
- Collez le code de la fonction
- Donnez-lui un nom explicite
2.1 GetHeaderParameters : Préparer les en-têtes HTTP
Cette fonction configure les en-têtes de base nécessaires pour nos requêtes HTTP. Elle définit par défaut le type de contenu en JSON et peut accepter des en-têtes supplémentaires si besoin.
let GetHeadersParameters = (optional Headers as record) => let Headers = Record.Combine({ [#"Content-Type" = "application/json"], (Headers ?? []) }) in Headersin GetHeadersParametersElle crée un enregistrement avec Content-Type: application/json et fusionne avec d’éventuels en-têtes supplémentaires que vous passez en paramètre.
2.2 GetQueryParameters : Gérer les paramètres de pagination
Cette fonction prépare les paramètres d’URL pour la pagination. Elle utilise les paramètres globaux qu’on a définis plus tôt.
let GetQueryParameters = (optional Skip as number, optional Query as record) => let Query = Record.Combine({ [ skip = Number.ToText(Skip ?? API_PAGINATION_SKIP), limit = Number.ToText(API_PAGINATION_LIMIT) ], (Query ?? []) }) in Queryin GetQueryParametersElle construit un objet avec les paramètres skip et limit pour la pagination, et peut inclure d’autres paramètres si vous en passez.
2.3 HttpClientGetRequest : Effectuer les requêtes GET
Voici notre fonction pour les requêtes GET. Elle utilise Web.Contents de Power Query pour faire l’appel HTTP.
let HttpClientGetRequest = ( RelativePath as text, optional Query as record, optional Headers as record ) => let Options = [ RelativePath = RelativePath, Query = (Query ?? []), Headers = (Headers ?? []) ], RawData = Web.Contents(API_BASE_URL, Options), Json = Json.Document(RawData) in Jsonin HttpClientGetRequestElle combine l’URL de base avec le chemin relatif, ajoute les paramètres de requête et les en-têtes, fait l’appel HTTP, et retourne le résultat en JSON.
2.4 HttpClientPostRequest : Effectuer les requêtes POST
Même principe que pour GET, mais pour les requêtes POST qui incluent un corps de requête.
let HttpClientPostRequest = ( RelativePath as text, optional Query as record, optional Headers as record, optional Body as record ) => let Options = [ RelativePath = RelativePath, Query = (Query ?? []), Headers = (Headers ?? []), Content = Json.FromValue(Body ?? []) ], RawData = Web.Contents(API_BASE_URL, Options), Json = Json.Document(RawData) in Jsonin HttpClientPostRequestPareil que GET, mais avec un paramètre Body en plus. Le fait d’ajouter Content à l’option transforme automatiquement la requête en POST.
Pourquoi deux fonctions séparées ?
Vous vous demandez peut-être pourquoi je ne fais pas une seule fonction générique qui gère GET et POST ? Bonne question ! J’ai essayé, et ça fonctionne… mais ça pose un problème.
Cliquez pour voir la fonction générique et comprendre le problème
let HttpClient = ( RelativePath as text, optional Query as record, optional Headers as record, optional Body as record ) => let GetOptions = [ RelativePath = RelativePath, Query = (Query ?? []), Headers = (Headers ?? []) ], PostOptions = [ RelativePath = RelativePath, Query = (Query ?? []), Headers = (Headers ?? []), Content = Json.FromValue(Body ?? []) ], Options = if Body <> null and Record.FieldCount(Body) > 0 then PostOptions else GetOptions, RawData = Web.Contents(API_BASE_URL, Options), Json = Json.Document(RawData) in Jsonin HttpClientLe problème : Power Query considère les options de Web.Contents comme dynamiques (à cause du if), et ça génère un avertissement dans les paramètres de source de données. Résultat ? Vous ne pourrez pas planifier le rafraîchissement automatique dans le Power BI Service !
C’est pour ça que j’ai préféré séparer en deux fonctions distinctes. Si vous avez une meilleure solution, je suis tout ouïe dans les commentaires !
3. Gestion de l’authentification
Maintenant qu’on a nos fonctions de base, occupons-nous de l’authentification JWT.
3.1 GetJwtTokens : Obtenir les jetons d’accès
Cette fonction se connecte à l’API et récupère nos précieux jetons.
let GetJwtTokens = () => let Headers = GetHeadersParameters(), Query = [], Body = [ username = API_LOGIN_USERNAME, password = API_LOGIN_PASSWORD ], Response = HttpClientPostRequest("auth/login", Query, Headers, Body), JwtTokens = [ accessToken = Record.Field(Response, "accessToken"), refreshToken = Record.Field(Response, "refreshToken") ] in JwtTokensin GetJwtTokensElle envoie une requête POST avec vos identifiants à /auth/login et extrait les jetons d’accès et de rafraîchissement de la réponse.
3.2 GetHeadersParametersWithAccessToken : Ajouter le jeton aux en-têtes
Cette fonction combine tout ce qu’on a fait jusqu’ici : elle récupère le jeton et l’ajoute aux en-têtes HTTP.
let GetHeadersParametersWithAccessToken = (optional Headers as record) => let JwtTokens = GetJwtTokens(), accessToken = Record.Field(JwtTokens, "accessToken"), Headers = Record.Combine({ GetHeadersParameters((Headers ?? [])), [Authorization = Text.Format("Bearer #{0}", {accessToken})] }) in Headersin GetHeadersParametersWithAccessTokenElle appelle GetJwtTokens(), récupère l’access token, et l’ajoute dans un en-tête Authorization: Bearer token. Comme ça, toutes vos requêtes authentifiées sont prêtes !
4. Récupération des données et gestion de la pagination
On arrive au cœur du système : la fonction qui va tout orchestrer pour récupérer les données, page par page.
let FetchDataFromApi = ( RelativePath as text, ResultsFieldName as text, TotalFieldName as text ) => let // Fonction interne : récupère le nombre total d'éléments RetrieveTotalCount = (FieldName as text) => let ApiResponse = HttpClientGetRequest( RelativePath, GetQueryParameters(), GetHeadersParametersWithAccessToken() ), TotalCount = Record.Field(ApiResponse, FieldName) in TotalCount,
// Fonction interne : récupère une page spécifique FetchPage = (FieldName as text, PageIndex as number) => let Offset = (PageIndex ?? 0) * API_PAGINATION_LIMIT, QueryParams = GetQueryParameters(Offset), AuthHeaders = GetHeadersParametersWithAccessToken(), ApiResponse = HttpClientGetRequest( RelativePath, QueryParams, AuthHeaders ), Results = Record.Field(ApiResponse, FieldName) in Results,
// Fonction principale : récupère toutes les pages GetAllData = () => let TotalCount = RetrieveTotalCount(TotalFieldName), MaxCount = List.Max({API_PAGINATION_LIMIT, TotalCount}), TotalPages = Number.RoundUp(MaxCount / API_PAGINATION_LIMIT), PageIndices = {0..TotalPages - 1}, AllPages = List.Transform( PageIndices, each FetchPage(ResultsFieldName, _) ), CombinedPages = List.Union(AllPages), DataTable = Table.FromList( CombinedPages, Splitter.SplitByNothing(), null, null, ExtraValues.Error ), ColumnIndices = List.Positions(Table.ColumnNames(DataTable)), RecordColumnIndex = List.First( List.Select( ColumnIndices, each Type.Is( Value.Type(Record.FieldValues(DataTable{0}){_}), type record ) ) ), RecordColumnName = Table.ColumnNames(DataTable){RecordColumnIndex}, RecordFieldNames = Record.FieldNames( Record.FieldValues(DataTable{0}){RecordColumnIndex} ), ExpandedTable = Table.ExpandRecordColumn( DataTable, RecordColumnName, RecordFieldNames, RecordFieldNames ) in ExpandedTable in GetAllData()in FetchDataFromApiJe ne vais pas entrer dans les détails de chaque ligne de code, je vous encourage à examiner le code pour bien comprendre son fonctionnement. Mais voici ce que fait cette fonction :
-
Elle a besoin de trois infos :
RelativePath: le chemin de l’API (exemple : “auth/products”)ResultsFieldName: le nom du champ qui contient les résultats (exemple : “products”)TotalFieldName: le nom du champ qui indique le total (exemple : “total”)
-
Elle compte d’abord : Elle fait un premier appel juste pour savoir combien il y a d’éléments au total. Comme quand vous regardez le nombre de pages d’un livre avant de le lire
-
Elle récupère page par page : Elle calcule combien de pages il faut télécharger, puis elle va chercher chaque page l’une après l’autre
-
Elle assemble le tout : Toutes les pages sont combinées en une seule grande liste
-
Elle nettoie et structure : La liste est transformée en un beau tableau Power Query, avec toutes les colonnes bien dépliées et organisées
En gros, vous lui donnez le chemin de l’API et les noms des champs importants, et elle vous rend un tableau complet avec toutes les données. Magique, non ?
5. Organisation de vos fonctions
Petit conseil d’organisation : créez des dossiers dans Power Query pour ranger vos paramètres et fonctions. Ça aide vraiment à maintenir un projet propre !
Voici comment je structure mes projets par exemple :
Importation des données dans Power BI (Utilisation de la fonction FetchDataFromApi)
Maintenant que tout est en place, voyons à quel point c’est simple d’utiliser notre travail !
Récupérer les produits
Créez une nouvelle requête vide et collez ce code :
let Source = FetchDataFromApi("auth/products", "products", "total")in SourceC’est tout ! Vraiment, c’est tout.
Cliquez sur Terminer et admirez vos données qui se chargent :
Récupérer les utilisateurs
Même principe, c’est tout aussi simple :
let Source = FetchDataFromApi("auth/users", "users", "total")in SourceVous voyez ? Une fois que vous avez fait le travail de base, ajouter de nouvelles sources de données devient un jeu d’enfant !
Visualisez vos dépendances
Power Query propose une vue super pratique appelée “Dépendances de requêtes” qui vous montre graphiquement comment toutes vos fonctions et requêtes sont connectées. C’est parfait pour :
- Comprendre la structure de votre projet d’un coup d’œil
- Vérifier que tout est bien relié
- Repérer d’éventuels problèmes de dépendance
Pour y accéder : Onglet Affichage > Dépendances de requêtes
Conclusion
Voilà, on a fait le tour !
Dans cet article, on a vu ensemble comment :
- Structurer proprement un projet Power Query avec des paramètres et des fonctions réutilisables
- Gérer l’authentification JWT avec une API
- Récupérer automatiquement toutes les pages de données grâce à la pagination
- Transformer tout ça en tableaux propres et prêts à l’emploi dans Power BI
Le gros avantage de cette approche, c’est que maintenant vous avez un système réutilisable. Besoin de connecter une nouvelle API ? Vous n’avez qu’à adapter les paramètres et les chemins, le reste est déjà fait !
Quelques conseils pour aller plus loin :
- Adaptez le code à vos propres API (elles fonctionnent toutes un peu différemment)
- Ajoutez de la gestion d’erreurs si nécessaire
- Pensez à documenter vos fonctions avec des commentaires
- Testez toujours
Si vous avez apprécié cette approche basée sur des fonctions réutilisables, vous allez adorer ces deux autres articles qui suivent la même philosophie :
🔗 Gestion flexible des sources de données
Vous gérez des tableaux de bord qui doivent fonctionner sur différents environnements ? Découvrez comment adapter dynamiquement vos connexions MySQL selon l’environnement (développement, test, production). C’est le complément parfait de cet article si vous travaillez avec des bases de données volumineuses et que vous voulez éviter de reconfigurer manuellement vos sources à chaque fois.
📁 Importer plusieurs fichiers Excel/CSV proprement
Marre de voir votre éditeur Power Query pollué par des dizaines de requêtes quand vous importez plusieurs fichiers ? Je vous montre comment créer une fonction personnalisée pour combiner des fichiers Excel ou CSV de façon élégante.