Calling APIs and keeping secrets

A tool that calls a web API needs a URL, parameters, a key and a way to read the JSON that comes back. Trace a weather lookup that reads its key from the environment, then see what happens when the key is missing.

11 min Free

Most tools an agent uses end up calling a web API: send an HTTP request with some parameters, get JSON back, pick out the fields you need. Two libraries make this short in Python, requests and httpx, and they share the same shape:

import requests

r = requests.get(url, params={"q": "Chennai"}, headers={"user-agent": "my-app/0.0.1"}, timeout=10)
r.raise_for_status()      # raises HTTPError for 4xx and 5xx responses
data = r.json()           # parsed JSON: dicts, lists, numbers, strings

The second half of the job is the key. Almost every API wants a secret token, and the rule is simple: the key never goes in the code. It lives in an environment variable, and the program reads it with os.environ["WEATHER_API_KEY"]. Locally you keep the variable in a .env file that is listed in .gitignore; in production the platform injects it.

The program below does exactly that, with one change: fake_get and FakeResponse stand in for requests.get and its response, so the lesson makes no real network call and needs no real key. The rest of the code is unchanged from what you would ship. (Python’s unittest.mock is the standard way to build such stand-ins in tests; here a seven-line class is enough.)

Press Run to trace the program with WEATHER_API_KEY set to a dummy value. Then switch Try it to a shell where the variable is missing.

Ready
# Call a weather API with a key from the environment.
# No real network here: fake_get stands in for requests.get.
import json
import os

api_key = os.environ["WEATHER_API_KEY"]  # KeyError if it is not set
city = "Chennai"


class FakeResponse:
    status_code = 200
    text = '{"name": "Chennai", "main": {"temp": 31.2, "humidity": 74}}'

    def json(self):
        return json.loads(self.text)


def fake_get(url, params, timeout):
    print("GET", url, "for", params["q"])  # never print the key
    return FakeResponse()


url = "https://api.example.com/weather"
params = {"q": city, "appid": api_key, "units": "metric"}
response = fake_get(url, params=params, timeout=10)

print("status:", response.status_code)
data = response.json()
temp = data["main"]["temp"]
print(f"{data['name']}: {temp} C, humidity {data['main']['humidity']}%")

Press Play to run the program line by line, or step with ← →.

00/11

Variables

Nothing defined yet.

Output

Nothing printed yet.

What to notice

  1. The key comes from outside. os.environ["WEATHER_API_KEY"] reads a variable the shell or platform set before Python started. The source file contains no secret, so it is safe to commit and share.
  2. Missing means KeyError. os.environ behaves like a dict. A missing name stops the program on that line, before any request is made. os.getenv("NAME") returns None instead, which is only better if you check for it.
  3. Parameters are a dict. params={"q": city, ...} becomes the query string. The library handles encoding, so you never build URLs with string concatenation.
  4. Always pass a timeout. The requests docs say nearly all production code should. Without one, a stuck server means a stuck tool and a stuck agent.
  5. Check the status before the body. status_code == 200 or raise_for_status(). A 401 usually means the key is wrong or missing from the request.
  6. Never print the key. The program logs the URL and the city, not params. Keys in logs are the most common way they leak.

Keeping the key out of git

Create a file named .env next to your code (this is the format python-dotenv reads):

WEATHER_API_KEY=your-real-key-here

Add the file name to .gitignore so it is never committed:

.env

Then load it at the start of the program with python-dotenv (pip install python-dotenv):

import os
from dotenv import load_dotenv

load_dotenv()                            # reads .env into os.environ
api_key = os.environ["WEATHER_API_KEY"]  # same line as before

load_dotenv() does not overwrite a variable that is already set, so the same code works locally with .env and in production where the platform provides the variable.

If a key does get committed
Removing it in a later commit is not enough; the history still has it. Revoke the key in the provider’s console and issue a new one.

Why this matters for AI work

The weather tool in the agent lessons is this program with the fake removed. ADK calls your function with the arguments the model chose (city), your function calls the API and returns a dict, and the model reads that dict. Every rule here applies to model API keys too: a GOOGLE_API_KEY belongs in the environment, never in agent.py, and never in a log line.

Check your understanding

1. Where should an API key live?
2. os.environ['API_KEY'] is run in a shell where API_KEY is not set. What happens?
3. A tool calls requests.get(url) with no timeout and the server never answers. What does the agent see?