DEV Community

Alex Spinov
Alex Spinov

Posted on

I Track Every API I Use in a Single JSON File — Here's My System

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

Why This Works

  1. One source of truth — no more searching Slack for API keys
  2. Auth abstracted — switch between bearer, API key, query param without code changes
  3. Notes field — remember that weird quirk about date formats
  4. 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)