# UrlFetchApp voor externe API-calls [[TOC]] ## UrlFetchApp basis UrlFetchApp geeft Apps Script de mogelijkheid om HTTP-verzoeken te doen naar externe diensten. Je kunt REST-APIs aanroepen, webhooks triggeren, bestanden downloaden en data naar externe systemen sturen. ```javascript function basisGet() { const response = UrlFetchApp.fetch('https://httpbin.org/get'); const statusCode = response.getResponseCode(); const body = response.getContentText(); Logger.log(`Status: ${statusCode}`); Logger.log(body.substring(0, 200)); } ``` ## GET met query-parameters ```javascript function getMetParameters() { const basisUrl = 'https://api.voorbeeld.nl/producten'; const params = { categorie: 'elektronica', pagina: 1, perPagina: 50, sortering: 'prijs_asc', }; const queryString = Object.entries(params) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&'); const url = `${basisUrl}?${queryString}`; const response = UrlFetchApp.fetch(url, {muteHttpExceptions: true}); if (response.getResponseCode() !== 200) { Logger.log(`Fout: ${response.getResponseCode()}: ${response.getContentText()}`); return null; } return JSON.parse(response.getContentText()); } ``` ## POST met JSON body ```javascript function postJsonData() { const url = 'https://api.voorbeeld.nl/orders'; const data = { klantId: 'KL-001', producten: [{id: 'P-123', aantal: 2}, {id: 'P-456', aantal: 1}], bezorgAdres: 'Damrak 1, Amsterdam', }; const opties = { method: 'POST', contentType: 'application/json', payload: JSON.stringify(data), headers: { 'Authorization': 'Bearer ' + PropertiesService.getScriptProperties().getProperty('API_TOKEN'), 'X-Request-ID': Utilities.getUuid(), }, muteHttpExceptions: true, }; const response = UrlFetchApp.fetch(url, opties); const statusCode = response.getResponseCode(); if (statusCode >= 200 && statusCode < 300) { Logger.log('Order aangemaakt: ' + response.getContentText()); return JSON.parse(response.getContentText()); } else { throw new Error(`API-fout ${statusCode}: ${response.getContentText()}`); } } ``` :::warn title="Zet muteHttpExceptions aan bij eigen foutafhandeling" Gebruik altijd `muteHttpExceptions: true` als je HTTP-fouten zelf wilt afhandelen. Zonder deze optie gooit Apps Script een fout bij elke 4xx- of 5xx-respons, waarna je de response-body niet meer kunt lezen. ::: ## Authenticatie-patronen Bewaar tokens en wachtwoorden nooit hardcoded in je script. Zet ze in `PropertiesService.getScriptProperties()` of in Script properties via het projectinstellingenscherm, zodat ze niet in de broncode of in versiebeheer terechtkomen. ```javascript function basicAuth() { const gebruiker = 'api_gebruiker'; const wachtwoord = PropertiesService.getScriptProperties().getProperty('API_PASS'); const encoded = Utilities.base64Encode(`${gebruiker}:${wachtwoord}`); const response = UrlFetchApp.fetch('https://api.voorbeeld.nl/data', { headers: { 'Authorization': `Basic ${encoded}`, }, }); return JSON.parse(response.getContentText()); } function bearerToken() { const token = PropertiesService.getScriptProperties().getProperty('BEARER_TOKEN'); const response = UrlFetchApp.fetch('https://api.voorbeeld.nl/me', { headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json', }, }); return JSON.parse(response.getContentText()); } ``` :::tip title="Gebruik OAuth waar het kan" Voor Google-eigen APIs en veel populaire diensten kun je in plaats van handmatige tokens de `OAuth2`-bibliotheek of `ScriptApp.getOAuthToken()` gebruiken. Dat voorkomt dat je zelf langlevende geheimen moet beheren en verlengen. ::: ## Parallelle aanroepen met fetchAll Met `fetchAll` voer je meerdere verzoeken tegelijk uit in plaats van één voor één. Dat scheelt veel doorlooptijd als je tientallen onafhankelijke aanroepen moet doen. ```javascript function parallelleAanroepen() { const urls = [ 'https://api.voorbeeld.nl/gebruiker/1', 'https://api.voorbeeld.nl/gebruiker/2', 'https://api.voorbeeld.nl/gebruiker/3', ]; const verzoeken = urls.map(url => ({ url, method: 'GET', headers: { 'Authorization': 'Bearer ' + PropertiesService.getScriptProperties().getProperty('API_TOKEN'), }, muteHttpExceptions: true, })); const responses = UrlFetchApp.fetchAll(verzoeken); responses.forEach((response, idx) => { if (response.getResponseCode() === 200) { const data = JSON.parse(response.getContentText()); Logger.log(`Gebruiker ${idx + 1}: ${data.naam}`); } }); } ``` ## Bestanden downloaden en uploaden ```javascript function downloadBestand(url, bestandsnaam) { const response = UrlFetchApp.fetch(url); const blob = response.getBlob(); blob.setName(bestandsnaam); const map = DriveApp.getFolderById('MAP_ID'); const bestand = map.createFile(blob); Logger.log(`Opgeslagen: ${bestand.getUrl()}`); return bestand; } function uploadBestandNaarApi(driveBestandId) { const bestand = DriveApp.getFileById(driveBestandId); const blob = bestand.getBlob(); const response = UrlFetchApp.fetch('https://upload.voorbeeld.nl/bestanden', { method: 'POST', payload: { bestand: blob, naam: bestand.getName(), }, }); return JSON.parse(response.getContentText()); } ``` ## Retry-patroon bij tijdelijke fouten Status `429` (te veel verzoeken) en `5xx`-fouten zijn meestal tijdelijk. Een korte wachttijd die per poging oploopt, voorkomt dat je de API verder overbelast. ```javascript function fetchMetRetry(url, opties = {}, maxPogingen = 3) { for (let poging = 1; poging <= maxPogingen; poging++) { try { const response = UrlFetchApp.fetch(url, {...opties, muteHttpExceptions: true}); const statusCode = response.getResponseCode(); if (statusCode === 429 || statusCode >= 500) { if (poging === maxPogingen) { throw new Error(`API-fout na ${maxPogingen} pogingen: ${statusCode}`); } Utilities.sleep(poging * 1000); continue; } return response; } catch (e) { if (poging === maxPogingen) throw e; Utilities.sleep(poging * 1000); } } } ``` :::howto title="Webhook naar Slack sturen" 1. Maak een Slack Incoming Webhook URL aan in je Slack-workspace. 2. Sla de webhook URL op in `PropertiesService.getScriptProperties()`. 3. Roep de URL aan met `UrlFetchApp.fetch()`, met `method: 'POST'`, `contentType: 'application/json'` en een JSON-payload zoals `JSON.stringify({text: 'Bericht'})`. 4. Verwerk de response-code: `200` is succes, alles anders behandel je als fout. ::: :::faq ### Hoe stuur ik form-encoded data? Gebruik `contentType: 'application/x-www-form-urlencoded'` en geef het payload als object mee. Apps Script serialiseert het object dan automatisch als form-encoded string. ### Kan ik HTTPS-certificaten negeren? Nee, Apps Script valideert altijd SSL-certificaten. Als een API een zelfondertekend certificaat heeft, kun je daar vanuit Apps Script niet mee verbinden. ### Wat is het quotum voor UrlFetchApp? Volgens de officiele quotapagina (gecontroleerd juni 2026) zijn URL Fetch-aanroepen beperkt tot 20.000 per dag voor gewone gmail.com-accounts en 100.000 per dag voor Google Workspace-accounts. De maximale responsgrootte is 50 MB per aanroep. Een aparte UrlFetch-tijdlimiet in minuten per dag bestaat niet meer; wel geldt de algemene uitvoeringslimiet van zes minuten per scriptrun. ### Hoe verwerk ik een XML-response? Gebruik `XmlService.parse(response.getContentText())` om XML te parsen. XmlService is een ingebouwde Apps Script-service voor XML-verwerking. ### Hoe voorkom ik dat ik mijn quotum opmaakt? Cache antwoorden met `CacheService` voor data die niet vaak verandert, bundel onafhankelijke verzoeken met `fetchAll` en vermijd onnodige herhaalde aanroepen in lussen. Zo blijf je ruim onder de dagelijkse limiet. ::: UrlFetchApp is de brug tussen Apps Script en de wereld van externe APIs. Combineer het met PropertiesService voor veilige credential-opslag en CacheService om API-aanroepen te minimaliseren.