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.
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.
# 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 ← →.
Variables
Nothing defined yet.
Output
Nothing printed yet.
What to notice
- 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. - Missing means KeyError.
os.environbehaves like a dict. A missing name stops the program on that line, before any request is made.os.getenv("NAME")returnsNoneinstead, which is only better if you check for it. - Parameters are a dict.
params={"q": city, ...}becomes the query string. The library handles encoding, so you never build URLs with string concatenation. - 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.
- Check the status before the body.
status_code == 200orraise_for_status(). A 401 usually means the key is wrong or missing from the request. - 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.
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.