16  Travailler avec des API

L’utilisateur souhaite accéder à des données via une API.

Tâche concernée et recommandation
  • En premier lieu, il est recommandé de vérifier s’il existe un package R spécifique à l’API que vous voulez utiliser.
  • S’il n’existe de package spécifique,il est recommandé d’utiliser le package httr pour transmettre des requêtes à l’API et le package jsonlite pour retravailler le résultat des requêtes afin de la transformer dans un format plus exploitable.
  • Pour accéder à des données disponibles sur le site https://www.insee.fr, il est recommandé d’utiliser l’un des packages suivants : doremifasol, insee, inseeLocalData. Le package apinsee permet de gérer le renouvellement des jetons de connexion.
  • Les jetons d’accès (token) doivent être utilisés sous forme de variable d’environnements, inscrits dans un fichier .Renviron qui n’est pas partagé.
  • Si l’accès à internet se fait par l’intermédiaire d’un proxy, sous Windows, il est recommandé d’utiliser la fonction curl::ie_get_proxy_for_url.

16.1 Rappels des notions essentielles sur les API

16.1.1 Qu’est-ce qu’une API ?

Une Application Programming Interface (ou API) est une interface de programmation qui permet d’utiliser une application existante pour restituer des données. Le terme d’API peut être paraître intimidant, mais il s’agit simplement d’une façon de mettre à disposition des données : plutôt que de laisser l’utilisateur consulter directement des bases de données (souvent volumineuses et complexes), l’API lui propose de formuler une requête qui est traitée par le serveur hébergeant la base de données, puis de recevoir des données en réponse à sa requête.

D’un point de vue informatique, une API est une porte d’entrée clairement identifiée par laquelle un logiciel offre des services à d’autres logiciels (ou utilisateurs). L’objectif d’une API est de fournir un point d’accès à une fonctionnalité qui soit facile à utiliser et qui masque les détails de la mise en oeuvre. Par exemple, l’API Sirene permet de récupérer la raison sociale d’une entreprise à partir de son identifiant Siren en interrogeant le référentiel disponible sur Internet directement depuis un script R, sans avoir à connaître tous les détails du répertoire Sirene.

À l’Insee comme ailleurs, la connexion entre les bases de données pour les nouveaux projets tend à se réaliser par des API. L’accès à des données par des API devient ainsi de plus en plus commun et est amené à devenir une compétence de base de tout utilisateur de données.

16.1.2 Avantages des API

Les API présentent de multiples avantages :

  • Les API rendent les programmes plus reproductibles. En effet, grâce aux API, il est possible de mettre à jour facilement les données utilisées par un programme si celles-ci évoluent. Cette flexibilité accrue pour l’utilisateur évite au producteur de données d’avoir à réaliser de multiples extractions, et réduit le problème de la coexistence de versions différentes des données.
  • Grâce aux API, l’utilisateur peut extraire facilement une petite partie d’une base de données plus conséquente.
  • Les API permettent de mettre à disposition des données tout en limitant le nombre de personnes ayant accès aux bases de données elles-mêmes.
  • Grâce aux API, il est possible de proposer des services sur mesure pour les utilisateurs (par exemple, un accès spécifique pour les gros utilisateurs).

16.1.3 Utilisation des API

Une API peut souvent être utilisée de deux façons : par une interface Web, et par l’intermédiaire d’un logiciel (R, Python…). Par ailleurs, les API peuvent être proposées avec un niveau de liberté variable pour l’utilisateur :

  • soit en libre accès (l’utilisation n’est pas contrôlée et l’utilisateur peut utiliser le service comme bon lui semble) ;
  • soit via la génération d’un compte et d’un jeton d’accès qui permettent de sécuriser l’utilisation de l’API et de limiter le nombre de requêtes.
Spécificité Insee

Les API mises à disposition des utilisateurs par l’Insee se trouvent dans le catalogue des API.

16.1.3.1 Consulter l’interface Web d’une API

Les API peuvent proposer une interface Web, mais ce n’est pas toujours le cas. Cette interface permet notamment :

  • de s’inscrire aux différents services ;
  • de visualiser les différentes requêtes proposées par les services ;
  • de lancer l’API depuis cette plateforme ;
  • de proposer une documentation sur les API.

L’utilisation de l’interface Web est utile dans une démarche exploratoire mais trouve rapidement ses limites, notamment lorsqu’on consulte régulièrement l’API. L’utilisateur va rapidement se rendre compte qu’il est beaucoup plus commode d’utiliser une API via un logiciel de traitement pour automatiser la consultation ou pour réaliser du téléchargement de masse. De plus, l’interface Web n’existe pas systématiquement pour toutes les API.

16.1.3.2 Requêter une API

Le mode principal de consultation d’une API consiste à adresser une requête à cette API via un logiciel adapté (R, Python, Java…). Comme pour l’utilisation d’une fonction, l’appel d’une API comprend des paramètres qui sont détaillées dans la documentation de l’API. Voici les éléments importants à avoir en tête sur les requêtes :

  • Le point d’entrée d’un service offert par une API se présente sous la forme d’une URL (adresse web). Chaque service proposé par une API a sa propre URL. Par exemple, dans le cas de l’API Sirene, l’URL à utiliser pour obtenir des informations sur un Siren est : https://api.insee.fr/entreprises/sirene/V3/siren/.

  • Cette URL doit être complétée avec différents paramètres qui précisent la requête (par exemple l’identifiant Siren). Ces paramètres viennent s’ajouter à l’URL (qui peut donc devenir très longue!). Chaque service proposé par une API a ses propres paramètres, détaillés dans la documentation. S’agissant de l’API Sirene, l’utilisateur intéressé peut retrouver dans la documentation le paramétrage de l’URL.

  • Lorsque l’utilisateur soumet sa requête, l’API lui renvoie une réponse structurée contenant l’ensemble des informations demandées. Le résultat envoyé par une API est majoritairement aux formats JSON ou XML (deux formats dans lesquels les informations sont hiérarchisées de manière emboîtée). Plus rarement, certains services proposent une information sous forme plate (de type csv).

  • Du fait de la dimension hiérarchique des formats JSON ou XML, le résultat n’est pas toujours facile à récupérer. Certains packages, comme jsonlite ou xml2, facilitent l’extraction de champs d’une sortie d’API. Dans certains cas, des packages spécifiques à une API ont été créés pour simplifier l’écriture d’une requête ou la récupération du résultat.

16.2 Quelques packages permettant une utilisation simple des API des données Insee

Nous allons voir ici quelques packages permettant de traiter l’information d’une API facilement :

16.2.1 Le package apinsee

Ce package est très utile pour l’utilisation des API mises à disposition sur le catalogue des API. En effet, pour ces API, pour pouvoir effectuer une requête, il est nécessaire de s’authentifier à différents niveaux :

  1. S’authentifier grâce à une clé personnelle, associée à un compte créé sur https://api.insee.fr ;
  2. Avoir un jeton d’accès temporaire. Ces jetons ont une durée de vie limitée et doivent régulièrement être renouvelés.

Ce package propose de générer facilement un jeton d’accès temporaire à partir de R. De cette façon, il n’y a plus besoin de naviguer entre le programme R et le catalogue des API pour renouveler un jeton.

Pour utiliser cette fonctionnalité, il faut configurer l’usage du package lors de la première utilisation. Plus précisément, il faut renseigner les variables d’environnement INSEE_APP_KEY et INSEE_APP_SECRET dans le fichier de configuration .Renviron de R. (voir la fiche [Personnaliser la configuration de R] pour une présentation détaillée des fichiers de configuration de R). Pour le faire, il suffit d’exécuter la commande suivante :

usethis::edit_r_environ("user")

Votre fichier .Renviron est alors ouvert par RStudio s’il existe déjà. Si le fichier .Renviron n’existe pas encore, il est automatiquement créé (vide), enregistré et ouvert par RStudio. Il convient d’y ajouter deux variables d’environnement, INSEE_APP_KEY et INSEE_APP_SECRET. Les lignes suivantes peuvent servir de modèle, en remplaçant la deuxième partie de chaque ligne par la clé du compte à utiliser :

# clef du consommateur
INSEE_APP_KEY=xxxxxxxxxxxxxxxxxx
# secret du consommateur
INSEE_APP_SECRET=yyyyyyyyyyyyyyyyyy
Note

Voici deux remarques sur le stockage des variables d’environnement :

  • L’option "user" dans l’utilisation de la fonction usethis::edit_r_environ() permet de stocker ces clés dans un fichier global, connu de tous les projets d’un utilisateur R. Cela évite, d’une part, de stocker la même information à deux endroits différents. D’autre part, cela évite d’associer des informations personnelles à un projet, qui doit être sous contrôle de version (voir la Fiche Utiliser R avec RStudio) car le fichier .Renviron est un fichier contenant des informations personnelles.

  • Si vous avez choisi l’option "project" lors de l’appel à usethis::edit_r_environ, il faut ajouter le .Renviron dans les fichiers à ne pas suivre avec Git grâce à la commande suivante :

usethis::edit_git_ignore(".Renviron")

Enfin, pour créer le token temporaire, il suffit d’exécuter :

token <- apinsee::insee_auth()

Ce token peut ensuite être utilisé comme valeur du paramètre token de la fonction httr::config() qui sert à contrôler les paramètres d’une requête vers internet faite par R

library(httr)
set_config(config(token = token))

Dès lors, vous pouvez accéder aux API de l’Insee auxquelles votre application a souscrit.

16.2.2 Le package doremifasol

Ce package permet entre autres de solliciter l’API Sirène dans une procédure de requêtage intégrée.

doremifasol s’appuie sur le package apinsee, il faut donc renseigner les variables d’environnement décrites ci-dessus. Il reste ensuite à préciser le type d’information souhaitée (unités légales, établissements…) et la requête via l’argument argsApi. Par exemple, pour lister tous les bouchers de la ville de Tourcoing :

bouchers_tourcoing <-
  telechargerDonnees(
    "SIRENE_SIRET",
    argsApi = list(q = "codeCommuneEtablissement:59599 AND activitePrincipaleUniteLegale:47.22Z")
  )

16.2.3 Le package inseeLocalData

Ce package permet de télécharger les données localisées à la commune, diffusées sur https://www.insee.fr dans la rubrique Chiffres détaillés, sous forme de cubes prédéfinis. Cette API est hébergée sur le catalogue des API de l’Insee. Une authentification par un jeton est donc nécessaire.

Le package comporte une fonction unique qui permet d’importer les données présentes dans l’API Données Locales dans une liste contenant 4 objets :

  • les données statistiques ;
  • les modalités de chaque variable ;
  • l’information sur la zone demandée ;
  • l’information sur la source et le jeu de données demandé.

Exemple d’utilisation du package pour importer le nombre d’entreprises et d’établissements en 2017 (en géographie au 01/01/2017) selon l’activité en 5 catégories et une indicatrice indiquant s’il s’agit d’une entreprise individuelle ou non pour la commune de Nantes :

library(inseeLocalData)

croisement <- "NA5_B-ENTR_INDIVIDUELLE"
jeu_donnees <- "GEO2017REE2017"
nivgeo <- "COM"
codgeo <- "44109" #CODE GEO DE NANTES
modalite <- "all.all"

donneesAPI <- get_dataset(jeton, jeu_donnees, croisement, modalite, nivgeo, codgeo)

donnees <- donneesAPI$donnees # pour accéder aux données
liste_code <- donneesAPI$liste_code # pour accéder aux nomenclatures
info_zone <- donneesAPI$info_zone # pour accéder aux données géographiques
source <- donneesAPI$source # pour accéder à la source

16.2.4 Le package insee

Ce package permet de télécharger les données et leurs métadonnées diffusées sur le service SDMX de la Base de données Macroéconomique de l’Insee (BDM). Cette API étant ouverte, son accès ne demande pas d’identification, ni de jeton. Il est uniquement nécessaire de déterminer les données souhaitées soit via une liste de catégories (idbank), soit via une liste de données (dataset).

Information Fonction
Liste des jeux de données insee::get_dataset_list()
Liste des séries insee::get_idbank_list()
library(insee)

# Importer les données à partir de leur idbank ou importer un dataset à partir de leur identifiant:
table_bp <- get_insee_dataset("BALANCE-PAIEMENTS")
indicateur_001694056 <- get_insee_idbank("001694056")

Il est possible de rajouter des filtres à ces fonctions afin de limiter le nombre de données importées (filtre sur la période, sur la date de mise à jour, sur les filtres).

16.2.5 Le package OECD

Ce package permet de télécharger les données mises à disposition sur le site de l’OCDE. Cette API étant ouverte, son accès ne demande pas d’identification, ni de jeton. Il est uniquement nécessaire de déterminer les données souhaitées.

Spécificité Insee

Ce package utilise la librairie rsmdx qui n’est pas compatible avec la technologie Direct Access. Il ne fonctionne pas en télétravail pour les postes nomades qui accèdent à internet par ce biais. En revanche il fonctionne sur site.

Voici quelques utilisations possibles de ce package :

library(OECD)

# Obtenir la liste des tables présentes sur le site :
dsets <- get_datasets()

# Voir les métadonnées d'une table, via l'ouverture d'une page web (ici la table DUR_D) :
browse_metadata("DUR_D")

# Importer une table de données (ici la table DUR_D) :
data <- get_dataset("DUR_D")

16.3 Exemple d’utilisation d’une API sans package

Les exemples précédents proposaient l’accès à une API par le biais d’un package. Pour lire les données d’une API ne possédant pas de package, il faut utiliser les deux packages R suivants :

  • le package httr pour lancer la requête ;
  • puis le package jsonlite pour transformer en data.frame le résultat de la requête (qui est structurée en JSON).
Note

Selon la structure du JSON récupéré, la manipulation du résultat d’une requête peut être assez fastidieuse avec R. Python propose des outils plus performants pour retravailler des JSON (le package json notamment). Heureusement, grâce au package reticulate, il est aisé de faire tourner un code Python dans une session R et récupérer le résultat dans un format de données (par exemple data.frame) de R. L’approche par les API étant plus fréquente en Python qu’en R, on trouve également plus de packages facilitant l’accès à des données par ce biais en Python.

16.3.1 Le package httr

Le package httr permet de se connecter aux sites web et de se connecter aux API.

Il est possible de configurer la connexion internet localement sans modifier les variables système :

  • set_config() permet de configurer l’accès internet utilisée par les fonctions du package.
  • use_proxy() permet de déterminer le proxy à utiliser. De nombreuses institutions utilisent passent par un intermédiaire, le proxy, pour accéder à internet. L’adresse du proxy est à ajouter au requête car sinon R ne sait pas communiquer avec internet. Il s’agit d’un paramètre à ajouter dans les options httr.
Note

Sous windows, le proxy peut être paramétré de la manière suivante:

proxy <- curl::ie_get_proxy_for_url()
httr::set_config(httr::use_proxy(proxy))

Le package httr permet, lorsqu’on effectue une requête GET (une requête d’accès au résultat d’une recherche), de récupérer le résultat sous la forme d’un texte à retravailler.

16.3.2 Accès à une API sans jeton

En général, un appel à une API via httr s’effectue ainsi de la manière suivante :

httr::content(
  httr::GET(url),             # url correspond à l'url à interroger
  as = "text",                # type de la sortie renvoyée
  httr::content_type_json(),  # type de la réponse de l'url
  encoding = "UTF-8"          # encodage de la réponse de l'url
)

Prenons par exemple l’API d’OpenFood Facts, une base de données alimentaire. Imaginons qu’on désire récupérer l’information sur un produit. Cela s’obtient de la manière suivante :

url <- "https://world.openfoodfacts.org/api/v0/product/3017620425400.json"

resultats <- 
  httr::content(
    httr::GET(url),             # url correspond à l'url à interroger
    as="text",                  # type de la sortie renvoyée
    httr::content_type_json(),  # type de la réponse de l'url
    encoding= "UTF-8"            # encodage de la réponse de l'url
  )

Le résultat est formaté sous forme de JSON, ce qui est pratique mais peu intelligible :

jsonlite::prettify(resultats)
{
    "code": "3017620425400",
    "product": {
        "_id": "3017620425400",
        "_keywords": [
            "rspo",
            "pate",
            "cacao",
            "de",
            "ferrero",
            "sucre",
            "et",
            "huile",
            "durable",
            "aux",
            "chocolat",
            "tartiner",
            "au",
            "petit-dejeuner",
            "noisette",
            "palme",
            "nutella",
            "produit"
        ],
        "added_countries_tags": [

        ],
        "additives_debug_tags": [
            "en-e322i-added"
        ],
        "additives_n": 1,
        "additives_old_n": 2,
        "additives_old_tags": [
            "en:e1403",
            "en:e322"
        ],
        "additives_original_tags": [
            "en:e322i"
        ],
        "additives_prev_original_tags": [
            "en:e322"
        ],
        "additives_tags": [
            "en:e322",
            "en:e322i"
        ],
        "allergens": "en:milk,en:nuts,en:soybeans",
        "allergens_debug_tags": [

        ],
        "allergens_from_ingredients": "en:nuts, NOISETTES , LAIT , SOJA, NOISETTES, LAIT, SOJA",
        "allergens_from_user": "(fr) en:milk,en:nuts,en:soybeans",
        "allergens_hierarchy": [
            "en:milk",
            "en:nuts",
            "en:soybeans"
        ],
        "allergens_tags": [
            "en:milk",
            "en:nuts",
            "en:soybeans"
        ],
        "amino_acids_prev_tags": [

        ],
        "amino_acids_tags": [

        ],
        "brands": "Nutella,Ferrero",
        "brands_debug_tags": [

        ],
        "brands_tags": [
            "nutella",
            "ferrero"
        ],
        "carbon_footprint_from_known_ingredients_debug": "en:hazelnut-oil 13% x 2.6 = 33.8 g - ",
        "carbon_footprint_percent_of_known_ingredients": 13,
        "categories": "Petit-déjeuners, Produits à tartiner, Produits à tartiner sucrés, Pâtes à tartiner, Pâtes à tartiner aux noisettes, Pâtes à tartiner au chocolat, Pâtes à tartiner aux noisettes et au cacao",
        "categories_hierarchy": [
            "en:breakfasts",
            "en:spreads",
            "en:sweet-spreads",
            "fr:pates-a-tartiner",
            "en:hazelnut-spreads",
            "en:chocolate-spreads",
            "en:cocoa-and-hazelnuts-spreads"
        ],
        "categories_lc": "fr",
        "categories_old": "Petit-déjeuners, Produits à tartiner, Produits à tartiner sucrés, Pâtes à tartiner, Pâtes à tartiner aux noisettes, Pâtes à tartiner au chocolat, Pâtes à tartiner aux noisettes et au cacao",
        "categories_properties": {
            "agribalyse_food_code:en": "31032",
            "agribalyse_proxy_food_code:en": "31032",
            "ciqual_food_code:en": "31032"
        },
        "categories_properties_tags": [
            "all-products",
            "categories-known",
            "agribalyse-food-code-31032",
            "agribalyse-food-code-known",
            "agribalyse-proxy-food-code-31032",
            "agribalyse-proxy-food-code-known",
            "ciqual-food-code-31032",
            "ciqual-food-code-known",
            "agribalyse-known",
            "agribalyse-31032"
        ],
        "categories_tags": [
            "en:breakfasts",
            "en:spreads",
            "en:sweet-spreads",
            "fr:pates-a-tartiner",
            "en:hazelnut-spreads",
            "en:chocolate-spreads",
            "en:cocoa-and-hazelnuts-spreads"
        ],
        "category_properties": {
            "ciqual_food_name:en": "Chocolate spread with hazelnuts"
        },
        "checkers": [

        ],
        "checkers_tags": [

        ],
        "ciqual_food_name_tags": [
            "chocolate-spread-with-hazelnuts"
        ],
        "cities_tags": [

        ],
        "code": "3017620425400",
        "codes_tags": [
            "code-13",
            "3017620425xxx",
            "301762042xxxx",
            "30176204xxxxx",
            "3017620xxxxxx",
            "301762xxxxxxx",
            "30176xxxxxxxx",
            "3017xxxxxxxxx",
            "301xxxxxxxxxx",
            "30xxxxxxxxxxx",
            "3xxxxxxxxxxxx"
        ],
        "compared_to_category": "en:cocoa-and-hazelnuts-spreads",
        "complete": 0,
        "completeness": 0.7625,
        "correctors": [

        ],
        "correctors_tags": [
            "teolemon",
            "scanbot",
            "stephane",
            "tacite",
            "nicolasleger",
            "segundo",
            "jgonzale92",
            "charlesnepote",
            "packbot"
        ],
        "countries": "France, Suisse",
        "countries_debug_tags": [

        ],
        "countries_hierarchy": [
            "en:france",
            "en:switzerland"
        ],
        "countries_lc": "fr",
        "countries_tags": [
            "en:france",
            "en:switzerland"
        ],
        "created_t": 1350234941,
        "creator": "openfoodfacts-contributors",
        "data_quality_bugs_tags": [

        ],
        "data_quality_errors_tags": [

        ],
        "data_quality_info_tags": [
            "en:packaging-data-incomplete",
            "en:ingredients-percent-analysis-ok",
            "en:carbon-footprint-from-known-ingredients-but-not-from-meat-or-fish",
            "en:ecoscore-extended-data-not-computed",
            "en:food-groups-1-known",
            "en:food-groups-2-known",
            "en:food-groups-3-unknown"
        ],
        "data_quality_tags": [
            "en:packaging-data-incomplete",
            "en:ingredients-percent-analysis-ok",
            "en:carbon-footprint-from-known-ingredients-but-not-from-meat-or-fish",
            "en:ecoscore-extended-data-not-computed",
            "en:food-groups-1-known",
            "en:food-groups-2-known",
            "en:food-groups-3-unknown",
            "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown",
            "en:ecoscore-packaging-unscored-shape",
            "en:ecoscore-production-system-no-label"
        ],
        "data_quality_warnings_tags": [
            "en:ecoscore-origins-of-ingredients-origins-are-100-percent-unknown",
            "en:ecoscore-packaging-unscored-shape",
            "en:ecoscore-production-system-no-label"
        ],
        "debug_param_sorted_langs": [
            "fr"
        ],
        "ecoscore_data": {
            "adjustments": {
                "origins_of_ingredients": {
                    "aggregated_origins": [
                        {
                            "epi_score": "0",
                            "origin": "en:unknown",
                            "percent": 100,
                            "transportation_score": null
                        }
                    ],
                    "epi_score": 0,
                    "epi_value": -5,
                    "origins_from_categories": [
                        "en:unknown"
                    ],
                    "origins_from_origins_field": [
                        "en:unknown"
                    ],
                    "transportation_score": 0,
                    "transportation_scores": {
                        "ad": 0,
                        "al": 0,
                        "at": 0,
                        "ax": 0,
                        "ba": 0,
                        "be": 0,
                        "bg": 0,
                        "ch": 0,
                        "cy": 0,
                        "cz": 0,
                        "de": 0,
                        "dk": 0,
                        "dz": 0,
                        "ee": 0,
                        "eg": 0,
                        "es": 0,
                        "fi": 0,
                        "fo": 0,
                        "fr": 0,
                        "gg": 0,
                        "gi": 0,
                        "gr": 0,
                        "hr": 0,
                        "hu": 0,
                        "ie": 0,
                        "il": 0,
                        "im": 0,
                        "is": 0,
                        "it": 0,
                        "je": 0,
                        "lb": 0,
                        "li": 0,
                        "lt": 0,
                        "lu": 0,
                        "lv": 0,
                        "ly": 0,
                        "ma": 0,
                        "mc": 0,
                        "md": 0,
                        "me": 0,
                        "mk": 0,
                        "mt": 0,
                        "nl": 0,
                        "no": 0,
                        "pl": 0,
                        "ps": 0,
                        "pt": 0,
                        "ro": 0,
                        "rs": 0,
                        "se": 0,
                        "si": 0,
                        "sj": 0,
                        "sk": 0,
                        "sm": 0,
                        "sy": 0,
                        "tn": 0,
                        "tr": 0,
                        "ua": 0,
                        "uk": 0,
                        "us": 0,
                        "va": 0,
                        "world": 0,
                        "xk": 0
                    },
                    "transportation_value": 0,
                    "transportation_values": {
                        "ad": 0,
                        "al": 0,
                        "at": 0,
                        "ax": 0,
                        "ba": 0,
                        "be": 0,
                        "bg": 0,
                        "ch": 0,
                        "cy": 0,
                        "cz": 0,
                        "de": 0,
                        "dk": 0,
                        "dz": 0,
                        "ee": 0,
                        "eg": 0,
                        "es": 0,
                        "fi": 0,
                        "fo": 0,
                        "fr": 0,
                        "gg": 0,
                        "gi": 0,
                        "gr": 0,
                        "hr": 0,
                        "hu": 0,
                        "ie": 0,
                        "il": 0,
                        "im": 0,
                        "is": 0,
                        "it": 0,
                        "je": 0,
                        "lb": 0,
                        "li": 0,
                        "lt": 0,
                        "lu": 0,
                        "lv": 0,
                        "ly": 0,
                        "ma": 0,
                        "mc": 0,
                        "md": 0,
                        "me": 0,
                        "mk": 0,
                        "mt": 0,
                        "nl": 0,
                        "no": 0,
                        "pl": 0,
                        "ps": 0,
                        "pt": 0,
                        "ro": 0,
                        "rs": 0,
                        "se": 0,
                        "si": 0,
                        "sj": 0,
                        "sk": 0,
                        "sm": 0,
                        "sy": 0,
                        "tn": 0,
                        "tr": 0,
                        "ua": 0,
                        "uk": 0,
                        "us": 0,
                        "va": 0,
                        "world": 0,
                        "xk": 0
                    },
                    "value": -5,
                    "values": {
                        "ad": -5,
                        "al": -5,
                        "at": -5,
                        "ax": -5,
                        "ba": -5,
                        "be": -5,
                        "bg": -5,
                        "ch": -5,
                        "cy": -5,
                        "cz": -5,
                        "de": -5,
                        "dk": -5,
                        "dz": -5,
                        "ee": -5,
                        "eg": -5,
                        "es": -5,
                        "fi": -5,
                        "fo": -5,
                        "fr": -5,
                        "gg": -5,
                        "gi": -5,
                        "gr": -5,
                        "hr": -5,
                        "hu": -5,
                        "ie": -5,
                        "il": -5,
                        "im": -5,
                        "is": -5,
                        "it": -5,
                        "je": -5,
                        "lb": -5,
                        "li": -5,
                        "lt": -5,
                        "lu": -5,
                        "lv": -5,
                        "ly": -5,
                        "ma": -5,
                        "mc": -5,
                        "md": -5,
                        "me": -5,
                        "mk": -5,
                        "mt": -5,
                        "nl": -5,
                        "no": -5,
                        "pl": -5,
                        "ps": -5,
                        "pt": -5,
                        "ro": -5,
                        "rs": -5,
                        "se": -5,
                        "si": -5,
                        "sj": -5,
                        "sk": -5,
                        "sm": -5,
                        "sy": -5,
                        "tn": -5,
                        "tr": -5,
                        "ua": -5,
                        "uk": -5,
                        "us": -5,
                        "va": -5,
                        "world": -5,
                        "xk": -5
                    },
                    "warning": "origins_are_100_percent_unknown"
                },
                "packaging": {
                    "non_recyclable_and_non_biodegradable_materials": 0,
                    "packagings": [
                        {
                            "ecoscore_material_score": 0,
                            "ecoscore_shape_ratio": 0.2,
                            "material": "en:plastic",
                            "non_recyclable_and_non_biodegradable": "maybe",
                            "shape": "en:lid"
                        },
                        {
                            "ecoscore_material_score": 72,
                            "ecoscore_shape_ratio": 0.1,
                            "material": "en:heavy-aluminium",
                            "shape": "en:seal"
                        },
                        {
                            "ecoscore_material_score": 81,
                            "ecoscore_shape_ratio": 1,
                            "material": "en:glass",
                            "shape": "en:pot"
                        },
                        {
                            "ecoscore_material_score": 0,
                            "ecoscore_shape_ratio": 1,
                            "material": "en:unknown",
                            "shape": "en:jar"
                        }
                    ],
                    "score": -41.8,
                    "value": -14,
                    "warning": "unscored_shape"
                },
                "production_system": {
                    "labels": [

                    ],
                    "value": 0,
                    "warning": "no_label"
                },
                "threatened_species": {

                }
            },
            "agribalyse": {
                "agribalyse_food_code": "31032",
                "agribalyse_proxy_food_code": "31032",
                "co2_agriculture": 2.7444684,
                "co2_consumption": 0,
                "co2_distribution": 0.017321188,
                "co2_packaging": 0.17097517,
                "co2_processing": 4.5534882,
                "co2_total": 7.666309138,
                "co2_transportation": 0.18005618,
                "code": "31032",
                "dqr": "2.54",
                "ef_agriculture": 0.26394245,
                "ef_consumption": 0,
                "ef_distribution": 0.0046415086,
                "ef_packaging": 0.018685146,
                "ef_processing": 0.24201201,
                "ef_total": 0.5452432926,
                "ef_transportation": 0.015962178,
                "is_beverage": 0,
                "name_en": "Chocolate spread with hazelnuts",
                "name_fr": "Pâte à tartiner chocolat et noisette",
                "score": 50,
                "version": "3.1"
            },
            "grade": "d",
            "grades": {
                "ad": "d",
                "al": "d",
                "at": "d",
                "ax": "d",
                "ba": "d",
                "be": "d",
                "bg": "d",
                "ch": "d",
                "cy": "d",
                "cz": "d",
                "de": "d",
                "dk": "d",
                "dz": "d",
                "ee": "d",
                "eg": "d",
                "es": "d",
                "fi": "d",
                "fo": "d",
                "fr": "d",
                "gg": "d",
                "gi": "d",
                "gr": "d",
                "hr": "d",
                "hu": "d",
                "ie": "d",
                "il": "d",
                "im": "d",
                "is": "d",
                "it": "d",
                "je": "d",
                "lb": "d",
                "li": "d",
                "lt": "d",
                "lu": "d",
                "lv": "d",
                "ly": "d",
                "ma": "d",
                "mc": "d",
                "md": "d",
                "me": "d",
                "mk": "d",
                "mt": "d",
                "nl": "d",
                "no": "d",
                "pl": "d",
                "ps": "d",
                "pt": "d",
                "ro": "d",
                "rs": "d",
                "se": "d",
                "si": "d",
                "sj": "d",
                "sk": "d",
                "sm": "d",
                "sy": "d",
                "tn": "d",
                "tr": "d",
                "ua": "d",
                "uk": "d",
                "us": "d",
                "va": "d",
                "world": "d",
                "xk": "d"
            },
            "missing": {
                "labels": 1,
                "origins": 1,
                "packagings": 1
            },
            "missing_data_warning": 1,
            "previous_data": {
                "agribalyse": {
                    "agribalyse_food_code": "31032",
                    "agribalyse_proxy_food_code": "31032",
                    "co2_agriculture": 8.7770996,
                    "co2_consumption": 0,
                    "co2_distribution": 0.014104999,
                    "co2_packaging": 0.18864842,
                    "co2_processing": 0.69167973,
                    "co2_total": 9.8742343,
                    "co2_transportation": 0.19708507,
                    "code": "31032",
                    "dqr": "2.54",
                    "ef_agriculture": 0.61477708,
                    "ef_consumption": 0,
                    "ef_distribution": 0.0045906531,
                    "ef_packaging": 0.020453714,
                    "ef_processing": 0.085674643,
                    "ef_total": 0.74366703,
                    "ef_transportation": 0.017824104,
                    "is_beverage": 0,
                    "name_en": "Chocolate spread with hazelnuts",
                    "name_fr": "Pâte à tartiner chocolat et noisette",
                    "score": 40
                },
                "grade": "d",
                "score": 21
            },
            "score": 31,
            "scores": {
                "ad": 31,
                "al": 31,
                "at": 31,
                "ax": 31,
                "ba": 31,
                "be": 31,
                "bg": 31,
                "ch": 31,
                "cy": 31,
                "cz": 31,
                "de": 31,
                "dk": 31,
                "dz": 31,
                "ee": 31,
                "eg": 31,
                "es": 31,
                "fi": 31,
                "fo": 31,
                "fr": 31,
                "gg": 31,
                "gi": 31,
                "gr": 31,
                "hr": 31,
                "hu": 31,
                "ie": 31,
                "il": 31,
                "im": 31,
                "is": 31,
                "it": 31,
                "je": 31,
                "lb": 31,
                "li": 31,
                "lt": 31,
                "lu": 31,
                "lv": 31,
                "ly": 31,
                "ma": 31,
                "mc": 31,
                "md": 31,
                "me": 31,
                "mk": 31,
                "mt": 31,
                "nl": 31,
                "no": 31,
                "pl": 31,
                "ps": 31,
                "pt": 31,
                "ro": 31,
                "rs": 31,
                "se": 31,
                "si": 31,
                "sj": 31,
                "sk": 31,
                "sm": 31,
                "sy": 31,
                "tn": 31,
                "tr": 31,
                "ua": 31,
                "uk": 31,
                "us": 31,
                "va": 31,
                "world": 31,
                "xk": 31
            },
            "status": "known"
        },
        "ecoscore_grade": "d",
        "ecoscore_score": 31,
        "ecoscore_tags": [
            "d"
        ],
        "editors": [
            "",
            "segundo",
            "teolemon",
            "tacite",
            "manu1400",
            "scanbot",
            "nicolasleger",
            "stephane"
        ],
        "editors_tags": [
            "segundo",
            "openfoodfacts-contributors",
            "scanbot",
            "teolemon",
            "packbot",
            "stephane",
            "manu1400",
            "tacite",
            "nicolasleger",
            "jgonzale92",
            "charlesnepote"
        ],
        "emb_codes": "",
        "emb_codes_20141016": "",
        "emb_codes_debug_tags": [

        ],
        "emb_codes_orig": "",
        "emb_codes_tags": [

        ],
        "entry_dates_tags": [
            "2012-10-14",
            "2012-10",
            "2012"
        ],
        "expiration_date": "",
        "expiration_date_debug_tags": [

        ],
        "food_groups": "en:sweets",
        "food_groups_tags": [
            "en:sugary-snacks",
            "en:sweets"
        ],
        "fruits-vegetables-nuts_100g_estimate": 0,
        "generic_name": "Pâte à tartiner aux noisettes",
        "generic_name_fr": "Pâte à tartiner aux noisettes",
        "generic_name_fr_debug_tags": [

        ],
        "id": "3017620425400",
        "image_front_small_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.200.jpg",
        "image_front_thumb_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.100.jpg",
        "image_front_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.400.jpg",
        "image_small_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.200.jpg",
        "image_thumb_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.100.jpg",
        "image_url": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.400.jpg",
        "images": {
            "1": {
                "sizes": {
                    "100": {
                        "h": 75,
                        "w": 100
                    },
                    "400": {
                        "h": 300,
                        "w": 400
                    },
                    "full": {
                        "h": 1920,
                        "w": 2560
                    }
                },
                "uploaded_t": 1350234942,
                "uploader": "openfoodfacts-contributors"
            },
            "2": {
                "sizes": {
                    "100": {
                        "h": 100,
                        "w": 75
                    },
                    "400": {
                        "h": 400,
                        "w": 300
                    },
                    "full": {
                        "h": 960,
                        "w": 720
                    }
                },
                "uploaded_t": 1358527087,
                "uploader": "openfoodfacts-contributors"
            },
            "front": {
                "geometry": "1632x1914-678-6",
                "imgid": "1",
                "normalize": null,
                "rev": "11",
                "sizes": {
                    "100": {
                        "h": 100,
                        "w": 85
                    },
                    "200": {
                        "h": 200,
                        "w": 171
                    },
                    "400": {
                        "h": 400,
                        "w": 341
                    },
                    "full": {
                        "h": 1914,
                        "w": 1632
                    }
                },
                "white_magic": null
            },
            "front_fr": {
                "geometry": "1632x1914-678-6",
                "imgid": "1",
                "normalize": null,
                "rev": "11",
                "sizes": {
                    "100": {
                        "h": 100,
                        "w": 85
                    },
                    "200": {
                        "h": 200,
                        "w": 171
                    },
                    "400": {
                        "h": 400,
                        "w": 341
                    },
                    "full": {
                        "h": 1914,
                        "w": 1632
                    }
                },
                "white_magic": null
            }
        },
        "informers": [
            "manu1400",
            "stephane"
        ],
        "informers_tags": [
            "manu1400",
            "stephane",
            "teolemon",
            "tacite",
            "nicolasleger",
            "segundo",
            "jgonzale92",
            "charlesnepote"
        ],
        "ingredients": [
            {
                "ciqual_proxy_food_code": "31016",
                "id": "en:sugar",
                "percent_estimate": 39.275,
                "percent_max": 57.9,
                "percent_min": 20.65,
                "rank": 1,
                "text": "Sucre",
                "vegan": "yes",
                "vegetarian": "yes"
            },
            {
                "ciqual_food_code": "16129",
                "from_palm_oil": "yes",
                "id": "en:palm-oil",
                "percent_estimate": 24.225,
                "percent_max": 35.45,
                "percent_min": 13,
                "rank": 2,
                "text": "Huile de palme",
                "vegan": "yes",
                "vegetarian": "yes"
            },
            {
                "ciqual_food_code": "15004",
                "id": "en:hazelnut",
                "percent": 13,
                "percent_estimate": 13,
                "percent_max": 13,
                "percent_min": 13,
                "rank": 3,
                "text": "NOISETTES",
                "vegan": "yes",
                "vegetarian": "yes"
            },
            {
                "ciqual_food_code": "19054",
                "id": "en:skimmed-milk-powder",
                "percent": 8.7,
                "percent_estimate": 8.7,
                "percent_max": 8.7,
                "percent_min": 8.7,
                "rank": 4,
                "text": "LAIT écrémé en poudre",
                "vegan": "no",
                "vegetarian": "yes"
            },
            {
                "ciqual_proxy_food_code": "18100",
                "id": "en:fat-reduced-cocoa",
                "percent": 7.4,
                "percent_estimate": 7.4,
                "percent_max": 7.4,
                "percent_min": 7.4,
                "rank": 5,
                "text": "cacao maigre",
                "vegan": "yes",
                "vegetarian": "yes"
            },
            {
                "has_sub_ingredients": "yes",
                "id": "en:emulsifier",
                "percent_estimate": 3.7,
                "percent_max": 7.4,
                "percent_min": 0,
                "rank": 6,
                "text": "émulsifiant"
            },
            {
                "id": "en:vanillin",
                "percent_estimate": 3.69999999999999,
                "percent_max": 7.4,
                "percent_min": 0,
                "rank": 7,
                "text": "vanilline"
            },
            {
                "ciqual_food_code": "42200",
                "id": "en:soya-lecithin",
                "percent_estimate": 3.7,
                "percent_max": 7.4,
                "percent_min": 0,
                "text": "lécithine de SOJA",
                "vegan": "yes",
                "vegetarian": "yes"
            }
        ],
        "ingredients_analysis": {
            "en:non-vegan": [
                "en:skimmed-milk-powder"
            ],
            "en:palm-oil": [
                "en:palm-oil"
            ],
            "en:vegan-status-unknown": [
                "en:vanillin"
            ],
            "en:vegetarian-status-unknown": [
                "en:vanillin"
            ]
        },
        "ingredients_analysis_tags": [
            "en:palm-oil",
            "en:non-vegan",
            "en:vegetarian-status-unknown"
        ],
        "ingredients_debug": [
            "Sucre",
            ",",
            null,
            null,
            null,
            " Huile de palme",
            ",",
            null,
            null,
            null,
            " NOISETTES 13%",
            ",",
            null,
            null,
            null,
            " LAIT écrémé en poudre 8",
            ",",
            null,
            null,
            null,
            "7%",
            ",",
            null,
            null,
            null,
            " cacao maigre 7",
            ",",
            null,
            null,
            null,
            "4%",
            ",",
            null,
            null,
            null,
            " émulsifiant ",
            ":",
            ":",
            null,
            null,
            "  lécithine ",
            "(",
            "(",
            null,
            null,
            "SOJA)",
            ",",
            null,
            null,
            null,
            " vanilline."
        ],
        "ingredients_from_or_that_may_be_from_palm_oil_n": 1,
        "ingredients_from_palm_oil_n": 1,
        "ingredients_from_palm_oil_tags": [
            "huile-de-palme"
        ],
        "ingredients_hierarchy": [
            "en:sugar",
            "en:added-sugar",
            "en:disaccharide",
            "en:palm-oil",
            "en:oil-and-fat",
            "en:vegetable-oil-and-fat",
            "en:palm-oil-and-fat",
            "en:hazelnut",
            "en:nut",
            "en:tree-nut",
            "en:skimmed-milk-powder",
            "en:dairy",
            "en:milk-powder",
            "en:fat-reduced-cocoa",
            "en:plant",
            "en:cocoa",
            "en:emulsifier",
            "en:vanillin",
            "en:soya-lecithin",
            "en:e322",
            "en:e322i"
        ],
        "ingredients_ids_debug": [
            "sucre",
            "huile-de-palme",
            "noisettes-13",
            "lait-ecreme-en-poudre-8",
            "7",
            "cacao-maigre-7",
            "4",
            "emulsifiant",
            "lecithine",
            "soja",
            "vanilline"
        ],
        "ingredients_n": 8,
        "ingredients_n_tags": [
            "8",
            "1-10"
        ],
        "ingredients_original_tags": [
            "en:sugar",
            "en:palm-oil",
            "en:hazelnut",
            "en:skimmed-milk-powder",
            "en:fat-reduced-cocoa",
            "en:emulsifier",
            "en:vanillin",
            "en:soya-lecithin"
        ],
        "ingredients_percent_analysis": 1,
        "ingredients_tags": [
            "en:sugar",
            "en:added-sugar",
            "en:disaccharide",
            "en:palm-oil",
            "en:oil-and-fat",
            "en:vegetable-oil-and-fat",
            "en:palm-oil-and-fat",
            "en:hazelnut",
            "en:nut",
            "en:tree-nut",
            "en:skimmed-milk-powder",
            "en:dairy",
            "en:milk-powder",
            "en:fat-reduced-cocoa",
            "en:plant",
            "en:cocoa",
            "en:emulsifier",
            "en:vanillin",
            "en:soya-lecithin",
            "en:e322",
            "en:e322i"
        ],
        "ingredients_text": "Sucre, Huile de palme, NOISETTES 13%, LAIT écrémé en poudre 8,7%, cacao maigre 7,4%, émulsifiant : lécithine (SOJA), vanilline.",
        "ingredients_text_debug": "Sucre, Huile de palme, NOISETTES 13%, LAIT écrémé en poudre 8,7%, cacao maigre 7,4%, émulsifiant :  lécithine (SOJA), vanilline.",
        "ingredients_text_fr": "Sucre, Huile de palme, NOISETTES 13%, LAIT écrémé en poudre 8,7%, cacao maigre 7,4%, émulsifiant : lécithine (SOJA), vanilline.",
        "ingredients_text_fr_debug_tags": [

        ],
        "ingredients_text_with_allergens": "Sucre, Huile de palme, <span class=\"allergen\">NOISETTES<\/span> <\/span>13%, <span class=\"allergen\">LAIT<\/span> <\/span>écrémé en poudre 8,7%, cacao maigre 7,4%, émulsifiant : lécithine (<span class=\"allergen\">SOJA<\/span>), vanilline.",
        "ingredients_text_with_allergens_fr": "Sucre, Huile de palme, <span class=\"allergen\">NOISETTES<\/span> <\/span>13%, <span class=\"allergen\">LAIT<\/span> <\/span>écrémé en poudre 8,7%, cacao maigre 7,4%, émulsifiant : lécithine (<span class=\"allergen\">SOJA<\/span>), vanilline.",
        "ingredients_that_may_be_from_palm_oil_n": 0,
        "ingredients_that_may_be_from_palm_oil_tags": [

        ],
        "ingredients_with_specified_percent_n": 3,
        "ingredients_with_specified_percent_sum": 29.1,
        "ingredients_with_unspecified_percent_n": 4,
        "ingredients_with_unspecified_percent_sum": 70.9,
        "ingredients_without_ciqual_codes": [
            "en:emulsifier",
            "en:vanillin"
        ],
        "ingredients_without_ciqual_codes_n": 2,
        "interface_version_created": "20120622",
        "interface_version_modified": "20150316.jqm2",
        "known_ingredients_n": 21,
        "labels": "en:Sustainable, Huile de palme durable, RSPO",
        "labels_hierarchy": [
            "en:sustainable",
            "en:sustainable-palm-oil",
            "en:roundtable-on-sustainable-palm-oil"
        ],
        "labels_lc": "fr",
        "labels_old": "en:Sustainable, Huile de palme durable, RSPO",
        "labels_tags": [
            "en:sustainable",
            "en:sustainable-palm-oil",
            "en:roundtable-on-sustainable-palm-oil"
        ],
        "lang": "fr",
        "lang_debug_tags": [

        ],
        "languages": {
            "en:french": 4
        },
        "languages_codes": {
            "fr": 4
        },
        "languages_hierarchy": [
            "en:french"
        ],
        "languages_tags": [
            "en:french",
            "en:1"
        ],
        "last_edit_dates_tags": [
            "2022-02-10",
            "2022-02",
            "2022"
        ],
        "last_editor": "packbot",
        "last_image_dates_tags": [
            "2013-01-18",
            "2013-01",
            "2013"
        ],
        "last_image_t": 1358527087,
        "last_modified_by": "packbot",
        "last_modified_t": 1703163083,
        "last_updated_t": 1707527480,
        "lc": "fr",
        "link": "http://www.ferrero.fr/nutella",
        "link_debug_tags": [

        ],
        "main_countries_tags": [

        ],
        "manufacturing_places": "",
        "manufacturing_places_debug_tags": [

        ],
        "manufacturing_places_tags": [

        ],
        "max_imgid": "2",
        "minerals_prev_tags": [

        ],
        "minerals_tags": [

        ],
        "misc_tags": [
            "en:packagings-number-of-components-4",
            "en:some-ingredients-with-specified-percent",
            "en:nutriscore-computed",
            "en:nutrition-fruits-vegetables-nuts-estimate-from-ingredients",
            "en:nutrition-all-nutriscore-values-known",
            "en:nutrition-fruits-vegetables-legumes-estimate-from-ingredients",
            "en:nutriscore-2021-same-as-2023",
            "en:nutriscore-2021-e-2023-e",
            "en:packagings-not-complete",
            "en:packagings-not-empty-but-not-complete",
            "en:packagings-not-empty",
            "en:ecoscore-extended-data-not-computed",
            "en:ecoscore-missing-data-warning",
            "en:ecoscore-missing-data-labels",
            "en:ecoscore-missing-data-origins",
            "en:ecoscore-missing-data-packagings",
            "en:ecoscore-computed",
            "en:ecoscore-changed"
        ],
        "new_additives_n": 1,
        "no_nutrition_data": "",
        "nova_group": 4,
        "nova_group_debug": "",
        "nova_groups": "4",
        "nova_groups_markers": {
            "3": [
                [
                    "ingredients",
                    "en:sugar"
                ],
                [
                    "ingredients",
                    "en:milk-powder"
                ]
            ],
            "4": [
                [
                    "additives",
                    "en:e322"
                ],
                [
                    "ingredients",
                    "en:emulsifier"
                ]
            ]
        },
        "nova_groups_tags": [
            "en:4-ultra-processed-food-and-drink-products"
        ],
        "nucleotides_prev_tags": [

        ],
        "nucleotides_tags": [

        ],
        "nutrient_levels": {
            "fat": "high",
            "salt": "low",
            "saturated-fat": "high",
            "sugars": "high"
        },
        "nutrient_levels_tags": [
            "en:fat-in-high-quantity",
            "en:saturated-fat-in-high-quantity",
            "en:sugars-in-high-quantity",
            "en:salt-in-low-quantity"
        ],
        "nutriments": {
            "carbohydrates": 57.3,
            "carbohydrates_100g": 57.3,
            "carbohydrates_unit": "g",
            "carbohydrates_value": 57.3,
            "carbon-footprint-from-known-ingredients_100g": 33.8,
            "carbon-footprint-from-known-ingredients_product": 254,
            "energy": 2273,
            "energy-kj": 2273,
            "energy-kj_100g": 2273,
            "energy-kj_unit": "kJ",
            "energy-kj_value": 2273,
            "energy-kj_value_computed": 2272.5,
            "energy_100g": 2273,
            "energy_unit": "kJ",
            "energy_value": 2273,
            "fat": 31.6,
            "fat_100g": 31.6,
            "fat_unit": "g",
            "fat_value": 31.6,
            "fiber": 3.4,
            "fiber_100g": 3.4,
            "fiber_unit": "g",
            "fiber_value": 3.4,
            "fruits-vegetables-legumes-estimate-from-ingredients_100g": 0,
            "fruits-vegetables-legumes-estimate-from-ingredients_serving": 0,
            "fruits-vegetables-nuts-estimate-from-ingredients_100g": 13,
            "fruits-vegetables-nuts-estimate-from-ingredients_serving": 13,
            "nova-group": 4,
            "nova-group_100g": 4,
            "nova-group_serving": 4,
            "nutrition-score-fr": 23,
            "nutrition-score-fr_100g": 23,
            "proteins": 6,
            "proteins_100g": 6,
            "proteins_unit": "g",
            "proteins_value": 6,
            "salt": 0.09398,
            "salt_100g": 0.09398,
            "salt_unit": "g",
            "salt_value": 0.09398,
            "saturated-fat": 10.9,
            "saturated-fat_100g": 10.9,
            "saturated-fat_unit": "g",
            "saturated-fat_value": 10.9,
            "sodium": 0.037592,
            "sodium_100g": 0.037592,
            "sodium_unit": "g",
            "sodium_value": 0.037592,
            "sugars": 56.7,
            "sugars_100g": 56.7,
            "sugars_unit": "g",
            "sugars_value": 56.7
        },
        "nutriscore": {
            "2021": {
                "category_available": 1,
                "data": {
                    "energy": 2273,
                    "energy_points": 6,
                    "energy_value": 2273,
                    "fiber": 3.4,
                    "fiber_points": 3,
                    "fiber_value": 3.4,
                    "fruits_vegetables_nuts_colza_walnut_olive_oils": 13,
                    "fruits_vegetables_nuts_colza_walnut_olive_oils_points": 0,
                    "fruits_vegetables_nuts_colza_walnut_olive_oils_value": 13,
                    "is_beverage": 0,
                    "is_cheese": 0,
                    "is_fat": 0,
                    "is_water": 0,
                    "negative_points": 26,
                    "positive_points": 3,
                    "proteins": 6,
                    "proteins_points": 3,
                    "proteins_value": 6,
                    "saturated_fat": 10.9,
                    "saturated_fat_points": 10,
                    "saturated_fat_value": 10.9,
                    "sodium": 37.592,
                    "sodium_points": 0,
                    "sodium_value": 37.6,
                    "sugars": 56.7,
                    "sugars_points": 10,
                    "sugars_value": 56.7
                },
                "grade": "e",
                "nutrients_available": 1,
                "nutriscore_applicable": 1,
                "nutriscore_computed": 1,
                "score": 23
            },
            "2023": {
                "category_available": 1,
                "data": {
                    "components": {
                        "negative": [
                            {
                                "id": "energy",
                                "points": 6,
                                "points_max": 10,
                                "unit": "kJ",
                                "value": 2273
                            },
                            {
                                "id": "sugars",
                                "points": 15,
                                "points_max": 15,
                                "unit": "g",
                                "value": 56.7
                            },
                            {
                                "id": "saturated_fat",
                                "points": 10,
                                "points_max": 10,
                                "unit": "g",
                                "value": 10.9
                            },
                            {
                                "id": "salt",
                                "points": 0,
                                "points_max": 20,
                                "unit": "g",
                                "value": 0.09
                            }
                        ],
                        "positive": [
                            {
                                "id": "fiber",
                                "points": 1,
                                "points_max": 5,
                                "unit": "g",
                                "value": 3.4
                            },
                            {
                                "id": "fruits_vegetables_legumes",
                                "points": 0,
                                "points_max": 5,
                                "unit": "%",
                                "value": 0
                            }
                        ]
                    },
                    "count_proteins": 0,
                    "count_proteins_reason": "negative_points_greater_than_or_equal_to_11",
                    "is_beverage": 0,
                    "is_cheese": 0,
                    "is_fat_oil_nuts_seeds": 0,
                    "is_red_meat_product": 0,
                    "is_water": 0,
                    "negative_points": 31,
                    "negative_points_max": 55,
                    "positive_nutrients": [
                        "fiber",
                        "fruits_vegetables_legumes"
                    ],
                    "positive_points": 1,
                    "positive_points_max": 10
                },
                "grade": "e",
                "nutrients_available": 1,
                "nutriscore_applicable": 1,
                "nutriscore_computed": 1,
                "score": 30
            }
        },
        "nutriscore_2021_tags": [
            "e"
        ],
        "nutriscore_2023_tags": [
            "e"
        ],
        "nutriscore_data": {
            "energy": 2273,
            "energy_points": 6,
            "energy_value": 2273,
            "fiber": 3.4,
            "fiber_points": 3,
            "fiber_value": 3.4,
            "fruits_vegetables_nuts_colza_walnut_olive_oils": 13,
            "fruits_vegetables_nuts_colza_walnut_olive_oils_points": 0,
            "fruits_vegetables_nuts_colza_walnut_olive_oils_value": 13,
            "grade": "e",
            "is_beverage": 0,
            "is_cheese": 0,
            "is_fat": 0,
            "is_water": 0,
            "negative_points": 26,
            "positive_points": 3,
            "proteins": 6,
            "proteins_points": 3,
            "proteins_value": 6,
            "saturated_fat": 10.9,
            "saturated_fat_points": 10,
            "saturated_fat_value": 10.9,
            "score": 23,
            "sodium": 37.592,
            "sodium_points": 0,
            "sodium_value": 37.6,
            "sugars": 56.7,
            "sugars_points": 10,
            "sugars_value": 56.7
        },
        "nutriscore_grade": "e",
        "nutriscore_score": 23,
        "nutriscore_score_opposite": -23,
        "nutriscore_tags": [
            "e"
        ],
        "nutriscore_version": "2021",
        "nutrition_data": "on",
        "nutrition_data_per": "100g",
        "nutrition_data_per_debug_tags": [

        ],
        "nutrition_data_prepared": "",
        "nutrition_data_prepared_per": "100g",
        "nutrition_data_prepared_per_debug_tags": [

        ],
        "nutrition_grade_fr": "e",
        "nutrition_grades": "e",
        "nutrition_grades_tags": [
            "e"
        ],
        "nutrition_score_beverage": 0,
        "nutrition_score_debug": "",
        "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients": 1,
        "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value": 0,
        "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients": 1,
        "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value": 13,
        "origins": "",
        "origins_hierarchy": [

        ],
        "origins_lc": "fr",
        "origins_old": "",
        "origins_tags": [

        ],
        "other_nutritional_substances_tags": [

        ],
        "packaging": "Plastique, Verre, Bocal, Couvercle, Couvercle en plastique, Opercule aluminium, Pot en verre",
        "packaging_hierarchy": [
            "en:plastic",
            "en:glass",
            "en:jar",
            "en:lid",
            "fr:Couvercle en plastique",
            "fr:Opercule aluminium",
            "fr:Pot en verre"
        ],
        "packaging_lc": "fr",
        "packaging_materials_tags": [
            "en:aluminium",
            "en:glass",
            "en:plastic"
        ],
        "packaging_old": "Plastique, Verre, Bocal, Couvercle, Couvercle en plastique, Opercule aluminium, Pot en verre",
        "packaging_old_before_taxonomization": "Bocal,Verre,Couvercle,Plastique, couvercle en plastique, opercule aluminium, pot en verre",
        "packaging_recycling_tags": [

        ],
        "packaging_shapes_tags": [
            "en:jar",
            "en:lid",
            "en:pot",
            "en:seal"
        ],
        "packaging_tags": [
            "en:plastic",
            "en:glass",
            "en:jar",
            "en:lid",
            "fr:couvercle-en-plastique",
            "fr:opercule-aluminium",
            "fr:pot-en-verre"
        ],
        "packagings": [
            {
                "material": "en:plastic",
                "shape": "en:lid"
            },
            {
                "material": "en:aluminium",
                "shape": "en:seal"
            },
            {
                "material": "en:glass",
                "shape": "en:pot"
            },
            {
                "shape": "en:jar"
            }
        ],
        "packagings_materials": {
            "all": {

            },
            "en:glass": {

            },
            "en:metal": {

            },
            "en:plastic": {

            },
            "en:unknown": {

            }
        },
        "packagings_n": 4,
        "photographers": [

        ],
        "photographers_tags": [
            "openfoodfacts-contributors"
        ],
        "pnns_groups_1": "Sugary snacks",
        "pnns_groups_1_tags": [
            "sugary-snacks",
            "known"
        ],
        "pnns_groups_2": "Sweets",
        "pnns_groups_2_tags": [
            "sweets",
            "known"
        ],
        "popularity_key": 1,
        "popularity_tags": [
            "at-least-5-scans-2019",
            "top-75-percent-scans-2019",
            "top-80-percent-scans-2019",
            "top-85-percent-scans-2019",
            "top-90-percent-scans-2019",
            "top-100000-fr-scans-2019",
            "top-country-fr-scans-2019",
            "at-least-5-fr-scans-2019",
            "bottom-25-percent-scans-2020",
            "bottom-20-percent-scans-2020",
            "bottom-15-percent-scans-2020",
            "top-90-percent-scans-2020",
            "top-country-fr-scans-2020"
        ],
        "product_name": "Nutella",
        "product_name_fr": "Nutella",
        "product_name_fr_debug_tags": [

        ],
        "product_quantity": "750",
        "purchase_places": "France",
        "purchase_places_debug_tags": [

        ],
        "purchase_places_tags": [
            "france"
        ],
        "quantity": "750 g",
        "quantity_debug_tags": [

        ],
        "removed_countries_tags": [

        ],
        "rev": 20,
        "scans_n": 1,
        "selected_images": {
            "front": {
                "display": {
                    "fr": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.400.jpg"
                },
                "small": {
                    "fr": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.200.jpg"
                },
                "thumb": {
                    "fr": "https://images.openfoodfacts.org/images/products/301/762/042/5400/front_fr.11.100.jpg"
                }
            }
        },
        "serving_size_debug_tags": [

        ],
        "sortkey": 1610107187,
        "states": "en:to-be-completed, en:nutrition-facts-completed, en:ingredients-completed, en:expiration-date-to-be-completed, en:packaging-code-to-be-completed, en:characteristics-to-be-completed, en:origins-to-be-completed, en:categories-completed, en:brands-completed, en:packaging-completed, en:quantity-completed, en:product-name-completed, en:photos-to-be-validated, en:packaging-photo-to-be-selected, en:nutrition-photo-to-be-selected, en:ingredients-photo-to-be-selected, en:front-photo-selected, en:photos-uploaded",
        "states_hierarchy": [
            "en:to-be-completed",
            "en:nutrition-facts-completed",
            "en:ingredients-completed",
            "en:expiration-date-to-be-completed",
            "en:packaging-code-to-be-completed",
            "en:characteristics-to-be-completed",
            "en:origins-to-be-completed",
            "en:categories-completed",
            "en:brands-completed",
            "en:packaging-completed",
            "en:quantity-completed",
            "en:product-name-completed",
            "en:photos-to-be-validated",
            "en:packaging-photo-to-be-selected",
            "en:nutrition-photo-to-be-selected",
            "en:ingredients-photo-to-be-selected",
            "en:front-photo-selected",
            "en:photos-uploaded"
        ],
        "states_tags": [
            "en:to-be-completed",
            "en:nutrition-facts-completed",
            "en:ingredients-completed",
            "en:expiration-date-to-be-completed",
            "en:packaging-code-to-be-completed",
            "en:characteristics-to-be-completed",
            "en:origins-to-be-completed",
            "en:categories-completed",
            "en:brands-completed",
            "en:packaging-completed",
            "en:quantity-completed",
            "en:product-name-completed",
            "en:photos-to-be-validated",
            "en:packaging-photo-to-be-selected",
            "en:nutrition-photo-to-be-selected",
            "en:ingredients-photo-to-be-selected",
            "en:front-photo-selected",
            "en:photos-uploaded"
        ],
        "stores": "",
        "stores_debug_tags": [

        ],
        "stores_tags": [

        ],
        "teams": "pain-au-chocolat,shark-attack,stakano,chocolatine",
        "teams_tags": [
            "pain-au-chocolat",
            "shark-attack",
            "stakano",
            "chocolatine"
        ],
        "traces": "",
        "traces_debug_tags": [

        ],
        "traces_from_ingredients": "",
        "traces_from_user": "(fr) ",
        "traces_hierarchy": [

        ],
        "traces_tags": [

        ],
        "unique_scans_n": 1,
        "unknown_ingredients_n": 0,
        "unknown_nutrients_tags": [

        ],
        "update_key": "20240209",
        "vitamins_prev_tags": [

        ],
        "vitamins_tags": [

        ]
    },
    "status": 1,
    "status_verbose": "product found"
}
 

Pour en faire une information exploitable, il est nécessaire de retraiter le résultat de la requête. Par exemple, pour n’extraire que le libellé et le nutriscore d’un produit, ainsi que son indice de transformation NOVA, il faut utiliser une boucle sur les différentes caractéristiques à extraire :

df <- data.frame(
  lapply(c("product_name","nova_groups","nutriscore_grade"), function(x){
    jsonlite::fromJSON(resultats, flatten = TRUE)$product[[x]]
  })
)
colnames(df) <- c("product_name","nova_groups","nutriscore_grade")
df
  product_name nova_groups nutriscore_grade
1      Nutella           4                e

16.3.3 Accès à une API avec jeton

Pour les API protégées par des jetons, il faut rajouter un paramètre d’identification. Les jetons d’authentification (token) étant des informations personnelles, il ne faut pas les faire figurer dans un script. Comme expliqué précédemment, ils peuvent être stockés sous forme de variable d’environnement, par exemple sous le nom MON_JETON_SECRET. Il suffit alors d’utiliser Sys.getenv pour récupérer la valeur derrière le nom MON_JETON_SECRET :

jeton <- Sys.getenv("MON_JETON_SECRET") # création d'une variable contenant le jeton

auth_header <- httr::add_headers('Authorization'= paste('Bearer',jeton)) # création d'une variable d'authentification

res <- httr::content(httr::GET(url),
                     auth_header, # ajout de la variable d'authentification
                     as="text", 
                     httr::content_type_json(), 
                     encoding='UTF-8')

16.3.4 Le package jsonlite

Ce package propose principalement la fonction fromJSON qui permet de convertir une résultat en format json en un objet R.

16.3.5 Temporisation

Pour l’utilisation d’API avec un jeton, comme celle proposées sur le catalogue des API de l’Insee, le nombre de requête par minute est limité (30 pour un compte standard sur le catalogue des API). Pour ne pas être bloqué par cette limite, il est important de temporiser les appels successifs en introduisant une latence. La fonction permettant cela est Sys.sleep. Par exemple, pour laisser 30 secondes d’attente, taper Sys.sleep(30).

Exemple d’utilisation sur l’API interne RMèS

Une API interne sur les métadonnées de l’Insee est disponible (programme RMès). Elle permet d’obtenir de manière simplifiée des métadonnées (sources, concepts, liste de codes géographiques). Comme cette API est disponible en interne à l’Insee, il n’y a pas, comme pour accéder à celles sur internet, de proxy.

Voici un exemple de requête pour accéder aux données au niveau division de la Naf-rev2 :

library(dplyr)
library(stringr)
library(httr)
library(jsonlite)

# L'API est disponible en interne. Il n'y a pas de proxy pour cette API
set_config(use_proxy(""))

# url de la requête : ici on souhaite afficher la naf rev 2
url <- "url_de_la_requete"


# connexion à l'API pour récupérer les données en JSON
res <- content(GET(url),
                     as="text", content_type_json(), encoding='UTF-8')

# transformation des données pour les transformer en dataframe
res_ok <- as.data.frame(fromJSON(res))

# travail de la table pour obtenir le niveau division de la NAF-rev2
division <- res_ok %>% 
  filter(str_detect(uri,'division')) %>% 
  select(c('code', 'intituleFr'))
Spécificité Insee

L’URL de cette API est uniquement disponible en interne à l’Insee. Elle n’est pas rendu publique pour des raisons de sécurité.

16.3.6 Exemple d’utilisation de l’API Sirene

Cet exemple va aller chercher les liens de succession pour un établissement :

library(apinsee)
library(httr)
library(jsonlite)

url <- "https://api.insee.fr/entreprises/sirene/V3/siret/liensSuccession?q=siretEtablissementPredecesseur:39478192600016"
url <- URLencode(url, reserved = TRUE)
token <- insee_auth()
set_config(config(token = token))
res <- content(GET(url, config(token = token)), 
               as="text", 
               content_type_json(), 
               encoding='UTF-8')

res <- fromJSON(res)
sortie <- as.data.frame(res$liensSuccession)

L’API Sirene permet d’effectuer des recherches multicritères. Dans ce cas, il faut séparer les codes par %20OR%20 (code HTML signifiant OR) :

url <- "https://api.insee.fr/entreprises/sirene/V3/siret/liensSuccession?q=siretEtablissementPredecesseur%3A39478192600016%20OR%20siretEtablissementPredecesseur%3A39488939800027"
token <- apinsee::insee_auth()
set_config(config(token = token))
res <- content(httr::GET(url, httr::config(token = token)), 
               as="text", 
               content_type_json(), 
               encoding='UTF-8')

res<-fromJSON(res)
sortie <- as.data.frame(res$liensSuccession)