Getting Data from APIs with Google Apps Script

Github Repo https://github.com/lsvekis/Google-Apps-Script-APIs-and-Gemini

In this project, your Hybrid AI Orchestrator pulls in real data from the web using APIs and then lets AI (local model + Gemini) interpret that data.

To do that, we need to understand one key tool:

UrlFetchApp — the Apps Script class that lets you call APIs.

In this lesson you’ll learn:

  • What an API is (quick recap)
  • How to call an API using UrlFetchApp.fetch()
  • How to handle JSON responses
  • How to add headers (for APIs that need them)
  • How to use Script Properties to swap APIs without changing code
  • How our fetchDataFromApi_() helper fits into the bigger AI project

🔹 1. Quick Recap: What Is an API?

Think of an API as a URL that gives you data instead of a web page.

Example:

https://catfact.ninja/fact

If you open that in a browser, you’ll see JSON:

{
  "fact": "Cats sleep 70% of their lives.",
  "length": 34
}

With Apps Script, we can call that URL with UrlFetchApp.fetch() and get the same JSON in code — and then pass it to AI.


🔹 2. Your First API Call in Apps Script

Create a new Apps Script project, and in Code.gs try this:

function testCatFactApi() {
  const url = 'https://catfact.ninja/fact';

  const res = UrlFetchApp.fetch(url);
  const text = res.getContentText();  // raw JSON string

  Logger.log('Raw response: ' + text);

  const data = JSON.parse(text);      // convert JSON string → object

  Logger.log('Cat fact: ' + data.fact);
}

Run testCatFactApi():

  • Open Executions or View → Logs
  • You should see a random cat fact

This is the basic pattern we’ll reuse:

  1. UrlFetchApp.fetch(url)
  2. getContentText()
  3. JSON.parse(...)

🔹 3. Adding Headers (for APIs That Require Them)

Some APIs need extra info — for example, a header that says:

“I want JSON, not HTML”

The icanhazdadjoke API is like that.

Example: Calling icanhazdadjoke with Apps Script

function testJokeApi() {
  const url = 'https://icanhazdadjoke.com/';

  const options = {
    method: 'get',
    headers: {
      Accept: 'application/json'
    },
    muteHttpExceptions: true
  };

  const res = UrlFetchApp.fetch(url, options);
  const code = res.getResponseCode();
  const body = res.getContentText();

  Logger.log('Status: ' + code);
  Logger.log('Body: ' + body);

  if (code >= 200 && code < 300) {
    const data = JSON.parse(body);
    Logger.log('Joke: ' + data.joke);
  }
}

🔍 headers: { Accept: 'application/json' }
tells the API: “Please respond with JSON.”


🔹 4. Using Script Properties to Configure the API URL

Instead of hard-coding the URL in your functions, you can store it in Script Properties.

That way:

  • You can switch APIs (weather → cat facts → jokes)
  • Without changing any code
  • Perfect for demos & teaching

Step 1 – Add a Script Property

In Apps Script:

  1. Click the gear icon (Project Settings)
  2. Scroll to Script properties → Add script property
  3. Add:
    • Name: DATA_API_URL
    • Value: e.g. https://catfact.ninja/fact

Step 2 – Read It in Code

Here’s a reusable helper:

function fetchDataFromApi_() {
  const props = PropertiesService.getScriptProperties();
  const dataApiUrl = props.getProperty('DATA_API_URL');

  if (!dataApiUrl) {
    throw new Error('DATA_API_URL is not set in Script properties.');
  }

  const res = UrlFetchApp.fetch(dataApiUrl, { muteHttpExceptions: true });
  const code = res.getResponseCode();
  const body = res.getContentText();

  if (code < 200 || code >= 300) {
    return {
      source: 'error',
      status: code,
      body
    };
  }

  try {
    return JSON.parse(body);
  } catch (e) {
    return {
      source: 'error',
      status: code,
      body,
      parseError: String(e)
    };
  }
}

Now, anywhere in your script, you can do:

function testDataApiProperty() {
  const data = fetchDataFromApi_();
  Logger.log(JSON.stringify(data, null, 2));
}

To switch APIs, just change DATA_API_URL in Script Properties — no code changes needed.


🔹 5. Plugging in Real Sample APIs

Here’s how to use the free APIs we talked about, all via DATA_API_URL.

✅ Option A — Weather (Open-Meteo)

DATA_API_URL:

https://api.open-meteo.com/v1/forecast?latitude=43.65107&longitude=-79.347015&current_weather=true

Then:

function testWeather() {
  const data = fetchDataFromApi_();
  Logger.log(JSON.stringify(data, null, 2));

  const cw = data.current_weather;
  if (cw) {
    Logger.log('Temperature: ' + cw.temperature + '°C');
    Logger.log('Wind speed: ' + cw.windspeed + ' km/h');
  }
}

✅ Option B — Cat Facts

DATA_API_URL:

https://catfact.ninja/fact

Then:

function testCatFact() {
  const data = fetchDataFromApi_();
  Logger.log('Cat fact: ' + data.fact);
}

✅ Option C — Bored API

DATA_API_URL:

https://www.boredapi.com/api/activity

Then:

function testBoredApi() {
  const data = fetchDataFromApi_();
  Logger.log('Activity: ' + data.activity);
  Logger.log('Type: ' + data.type);
}

✅ Option D — Jokes (with Headers)

For icanhazdadjoke, you’ll likely want a dedicated version that includes headers, like:

function fetchDataFromApi_() {
  const props = PropertiesService.getScriptProperties();
  const dataApiUrl = props.getProperty('DATA_API_URL') || 'https://icanhazdadjoke.com/';

  const options = {
    method: 'get',
    headers: {
      Accept: 'application/json'
    },
    muteHttpExceptions: true
  };

  const res = UrlFetchApp.fetch(dataApiUrl, options);
  const code = res.getResponseCode();
  const body = res.getContentText();

  if (code < 200 || code >= 300) {
    return {
      source: 'error',
      status: code,
      body
    };
  }

  try {
    return JSON.parse(body);
  } catch (e) {
    return {
      source: 'error',
      status: code,
      body,
      parseError: String(e)
    };
  }
}

Then:

function testJoke() {
  const data = fetchDataFromApi_();
  Logger.log('Joke: ' + data.joke);
}

🔹 6. How This Fits Into the Hybrid AI Orchestrator

In your Hybrid AI Orchestrator project, the same fetchDataFromApi_() helper is used inside chatHybrid(question):

function chatHybrid(question) {
  const q = (question || '').trim();
  if (!q) throw new Error('Question is empty.');

  // 1) Get data from API (this lesson)
  const data = fetchDataFromApi_();

  // 2) Ask local AI model to analyze it
  const localAnalysis = callLocalAi_(q, data);

  // 3) Ask Gemini to synthesize everything into a final answer
  const finalAnswer = callGeminiOrchestrator_(q, data, localAnalysis);

  return {
    answer: finalAnswer,
    raw: { data, localAnalysis }
  };
}

So this lesson is the foundation:

First, we learn how to get JSON data into Apps Script. Then, we plug that data into local AI + Gemini.


🔹 7. Practice Tasks (Apps Script Exercises)

You can add these as end-of-lesson exercises in the blog:

🧪 Exercise 1 — Swap APIs Without Editing Code

  1. Set DATA_API_URL to the cat facts API.
  2. Run testDataApiProperty() and log the fact.
  3. Change DATA_API_URL to the Bored API.
  4. Run the same function — see how the data changes without touching code.

🧪 Exercise 2 — Build a Simple “Explain This” Function

function explainDataForAi_() {
  const data = fetchDataFromApi_();
  return JSON.stringify(data, null, 2);
}

Later, you’ll feed explainDataForAi_() into Gemini as context.


🧪 Exercise 3 — Add Basic Error Handling

Update fetchDataFromApi_() to throw an error if source === 'error', and handle that gracefully in chatHybrid().