11 min de lecture

UrlFetchApp : appeler une API depuis Google Sheets avec Apps Script (GET, POST, JSON)

Récupérer des données d’une API dans un Google Sheets, ou y envoyer des lignes : UrlFetchApp.fetch en GET et en POST, lecture du JSON, clé API dans les en-têtes, gestion des erreurs avec muteHttpExceptions, appels groupés avec fetchAll, cache et quotas, avec des exemples prêts à copier.

  • Google Apps Script
  • API
  • Tutoriel
  • Automatisation
Schéma : clients et versions d’API SPA Mobile Partenaire API /api/v1 … v2 Compatibilité & déploiement

Un premier appel GET

Exemple avec l’API publique et gratuite geo.api.gouv.fr, qui ne demande pas de clé : retrouver la commune et sa population à partir d’un code postal.

function testApi() {
  const url = 'https://geo.api.gouv.fr/communes?codePostal=59193&fields=nom,population';
  const reponse = UrlFetchApp.fetch(url);
  const communes = JSON.parse(reponse.getContentText());
  Logger.log(communes); // [{nom=Erquinghem-Lys, population=…, code=…}]
}

L’API renvoie un tableau JSON ; JSON.parse le transforme en tableau d’objets JavaScript, que l’on parcourt comme n’importe quel tableau.

Remplir une colonne du classeur avec la réponse

Onglet Codes : les codes postaux en colonne A, le script remplit la ou les communes (B) et la population (C, celle de la première commune quand un code postal en couvre plusieurs).

function completerCommunes() {
  const feuille = SpreadsheetApp.getActive().getSheetByName('Codes');
  const derniere = feuille.getLastRow();
  if (derniere < 2) return;
  const codes = feuille.getRange(2, 1, derniere - 1, 1).getValues();
  const resultats = codes.map(([cp]) => {
    if (!cp) return ['', ''];
    const url = 'https://geo.api.gouv.fr/communes?codePostal=' +
      encodeURIComponent(String(cp).padStart(5, '0')) + '&fields=nom,population';
    const rep = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
    if (rep.getResponseCode() !== 200) return ['Erreur ' + rep.getResponseCode(), ''];
    const communes = JSON.parse(rep.getContentText());
    if (communes.length === 0) return ['Introuvable', ''];
    return [communes.map(c => c.nom).join(', '), communes[0].population || ''];
  });
  feuille.getRange(2, 2, resultats.length, 2).setValues(resultats); // une seule écriture
}

Deux réflexes à garder : encodeURIComponent pour tout paramètre inséré dans une URL (accents, espaces, &), et une seule écriture avec setValues à la fin plutôt qu’un setValue par ligne, beaucoup plus lent. padStart remet le zéro initial que Sheets supprime quand un code postal comme 02100 est saisi comme un nombre.

Un appel POST en JSON avec une clé API

La plupart des API (CRM, facturation, outils d’e-mailing) attendent une clé dans un en-tête et un corps JSON. La clé est lue dans les propriétés du script, jamais écrite dans le code :

function envoyerContact(contact) {
  const cle = PropertiesService.getScriptProperties().getProperty('API_KEY');
  if (!cle) throw new Error('Propriété API_KEY manquante.');
  const rep = UrlFetchApp.fetch('https://api.exemple.com/v1/contacts', {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + cle },
    payload: JSON.stringify(contact),
    muteHttpExceptions: true,
  });
  const code = rep.getResponseCode();
  if (code < 200 || code >= 300) {
    throw new Error('API ' + code + ' : ' + rep.getContentText().slice(0, 300));
  }
  return JSON.parse(rep.getContentText());
}

L’URL api.exemple.com est à remplacer par celle de votre service, et le format de l’en-tête par celui que sa documentation indique (Bearer, X-API-Key, authentification basique…). Pour une authentification basique, l’en-tête se construit avec 'Basic ' + Utilities.base64Encode(identifiant + ':' + motDePasse).

Sans JSON.stringify, un objet passé en payload est envoyé comme un formulaire (application/x-www-form-urlencoded), ce que beaucoup d’API JSON refusent.

Gérer les erreurs

Par défaut, fetch lève une exception dès que le serveur répond avec un code d’erreur (4xx, 5xx), et le message ne contient qu’une partie de la réponse. Avec muteHttpExceptions: true, vous récupérez la réponse complète et décidez quoi faire :

CodeSignification habituelleRéaction
401 / 403Clé absente, invalide ou sans droitsVérifier la propriété et les droits de la clé
404Ressource ou URL inexistanteVérifier l’URL et les identifiants
429Trop de requêtesAttendre puis réessayer
500 à 599Erreur côté serveurRéessayer plus tard
function fetchAvecReessai(url, options, essais = 3) {
  for (let n = 1; n <= essais; n++) {
    const rep = UrlFetchApp.fetch(url, Object.assign({ muteHttpExceptions: true }, options));
    const code = rep.getResponseCode();
    if (code !== 429 && code < 500) return rep;
    Utilities.sleep(1000 * Math.pow(2, n)); // 2 s, 4 s, 8 s
  }
  throw new Error('API indisponible après ' + essais + ' essais : ' + url);
}

Plusieurs appels en parallèle avec fetchAll

Quand il faut interroger l’API pour des dizaines de lignes, UrlFetchApp.fetchAll envoie les requêtes en parallèle et renvoie les réponses dans le même ordre :

const requetes = ['59193', '59000', '59280'].map(cp => ({
  url: 'https://geo.api.gouv.fr/communes?codePostal=' + cp + '&fields=nom',
  muteHttpExceptions: true,
}));
const reponses = UrlFetchApp.fetchAll(requetes);
reponses.forEach(r => Logger.log(r.getResponseCode() + ' ' + r.getContentText()));

C’est souvent ce qui permet à un traitement de rester sous la limite de 6 minutes par exécution. Respectez quand même les limites de débit de l’API appelée : envoyez les requêtes par paquets si elle renvoie des 429.

Éviter de rappeler l’API à chaque fois : le cache

function getTaux() {
  const cache = CacheService.getScriptCache();
  const enCache = cache.get('TAUX');
  if (enCache) return JSON.parse(enCache);
  const rep = UrlFetchApp.fetch('https://api.exemple.com/v1/taux', { muteHttpExceptions: true });
  if (rep.getResponseCode() !== 200) throw new Error('API ' + rep.getResponseCode());
  cache.put('TAUX', rep.getContentText(), 3600); // garde la réponse 1 heure
  return JSON.parse(rep.getContentText());
}

Pour des données qui changent peu (taux, listes de référence, catalogues), le cache réduit le nombre d’appels, donc le risque de dépasser les quotas, et accélère le script.

Quotas et limites

Limite (page des quotas Google)gmail.comGoogle Workspace
Appels UrlFetch par jour20 000100 000
Taille d’une réponse50 Mo50 Mo
Taille d’un envoi POST50 Mo50 Mo
Longueur de l’URL2 Ko2 Ko
Nombre d’en-têtes par appel100100

Les requêtes partent des serveurs de Google, pas de votre poste : une API accessible uniquement depuis le réseau de l’entreprise (intranet, serveur sans adresse publique) ne sera pas joignable depuis Apps Script.

Questions fréquentes

Peut-on appeler une API depuis une fonction personnalisée (=MAFONCTION()) ?

Oui, UrlFetchApp fait partie des services autorisés dans une fonction personnalisée. Mais elle est recalculée souvent et limitée à 30 secondes : pour plus de quelques cellules, préférez un script lancé par un menu ou un déclencheur temporisé qui écrit les valeurs.

Comment recevoir des données (webhook) plutôt que les demander ?

Un script publié comme application web avec une fonction doPost(e) peut recevoir des requêtes d’un autre service et écrire dans le classeur. C’est le sens inverse de UrlFetchApp.

IMPORTDATA ou UrlFetchApp ?

IMPORTDATA ou IMPORTXML suffisent pour lire un fichier CSV ou une page publique sans clé. Dès qu’il faut une clé API, du JSON, un POST ou un traitement des erreurs, il faut passer par Apps Script.

Connecter Google Sheets à des API fait partie de la formation Google Apps Script. Pour relier vos fichiers à un CRM, un outil de facturation ou une application métier, voir l’automatisation Google Sheets sur mesure, et pour envoyer le résultat par e-mail, envoyer des e-mails depuis Google Sheets.