I work with 30+ APIs across different projects. Keeping track of rate limits, auth tokens, base URLs, and quirks was driving me crazy.
So I built a simple system: one JSON file that tracks everything.
The File
{
"apis": {
"github": {
"base_url": "https://api.github.com",
"auth_type": "bearer",
"auth_env": "GITHUB_TOKEN",
"rate_limit": "5000/hour",
"docs": "https://docs.github.com/rest",
"notes": "Use Accept: application/vnd.github.v3+json header",
"last_used": "2026-03-25"
},
"devto": {
"base_url": "https://dev.to/api",
"auth_type": "api-key",
"auth_header": "api-key",
"auth_env": "DEVTO_API_KEY",
"rate_limit": "30/30s",
"docs": "https://developers.forem.com/api",
"notes": "Pagination: page + per_page params",
"last_used": "2026-03-25"
},
"fred": {
"base_url": "https://api.stlouisfed.org/fred",
"auth_type": "query_param",
"auth_param": "api_key",
"auth_env": "FRED_API_KEY",
"rate_limit": "unlimited",
"docs": "https://fred.stlouisfed.org/docs/api",
"notes": "Always add file_type=json, dates in YYYY-MM-DD",
"last_used": "2026-03-24"
}
}
}
The Helper Script
import json
import os
import httpx
from pathlib import Path
class APIRegistry:
def __init__(self, path="~/.api-registry.json"):
self.path = Path(path).expanduser()
self.data = json.loads(self.path.read_text()) if self.path.exists() else {"apis": {}}
def get(self, name):
api = self.data["apis"].get(name)
if not api:
raise KeyError(f"API '{name}' not in registry. Available: {list(self.data['apis'].keys())}")
# Build auth
headers = {}
params = {}
token = os.environ.get(api.get("auth_env", ""), "")
if api["auth_type"] == "bearer":
headers["Authorization"] = f"Bearer {token}"
elif api["auth_type"] == "api-key":
headers[api.get("auth_header", "X-API-Key")] = token
elif api["auth_type"] == "query_param":
params[api.get("auth_param", "api_key")] = token
return {"base_url": api["base_url"], "headers": headers, "params": params}
def call(self, name, endpoint, **kwargs):
config = self.get(name)
url = f"{config['base_url']}{endpoint}"
headers = {**config['headers'], **kwargs.pop('headers', {})}
params = {**config['params'], **kwargs.pop('params', {})}
return httpx.get(url, headers=headers, params=params, **kwargs)
# Usage
reg = APIRegistry()
response = reg.call("github", "/user/repos", params={"per_page": 5})
print(response.json())
Why This Works
- One source of truth — no more searching Slack for API keys
- Auth abstracted — switch between bearer, API key, query param without code changes
- Notes field — remember that weird quirk about date formats
- Portable — share the file (minus secrets) with your team
What I'd Add Next
- Rate limit tracking (count calls, warn when approaching limits)
- Auto-rotate tokens
- Team sharing via encrypted git repo
How Do You Track Your APIs?
Do you have a system, or is it scattered across .env files, browser bookmarks, and Notion pages?
I maintain 200+ free APIs — all tracked in this format.
Top comments (0)