API inventory from real traffic — no gateway, no database, no servers. Just a Cloudflare Worker and a Google Sheet.
A zero-infrastructure API inventory tool built on Cloudflare Workers and Google Sheets. Transparently proxies your API traffic, logs every request, and auto-builds a deduplicated endpoint registry — no API gateway, no database, no servers required.
You have a mobile/web app hitting a backend API but no API gateway, no OpenAPI spec, and no maintained inventory of what endpoints actually exist and get called in production. You need visibility without changing your app or standing up new infrastructure.
A Cloudflare Worker sits in front of your API as a transparent passthrough proxy. It forwards every request to your real origin untouched, and asynchronously logs the request metadata to two Google Sheets tabs:
Events— full request log (method, path, headers, body, status, latency, IP, country)Inventory— lightweight hit feed (timestamp, method, path, status, latency)Inventory_Clean— auto-aggregated endpoint registry via a QUERY formula (no worker logic, no race conditions)
Mobile App → Cloudflare Worker → Origin API
↓ (async, non-blocking)
Google Sheets (Events + Inventory tabs)
- Zero latency impact — logging happens in
waitUntil, response is returned immediately - Transparent — app and origin need no changes
- Auto-deduplication —
Inventory_Cleantab aggregates endpoints via Google Sheets QUERY formula - PII/secret redaction — sensitive headers and JSON body fields are redacted before logging
- Noise filtering — health checks, favicon, and root path hits are skipped
- Free — Cloudflare Workers free tier + Google Sheets free tier is more than enough for most teams
- No database — Google Sheets is the only storage backend
- A Cloudflare account with your API domain proxied (orange cloud ☁️ in DNS)
- A Google Cloud project with a Service Account
- A Google Sheet shared with the Service Account
- Wrangler CLI installed
git clone https://github.com/your-org/gateless.git
cd gateless- Go to Google Cloud Console → IAM & Admin → Service Accounts
- Click Create Service Account — skip role assignment
- Under the Keys tab → Add Key → JSON → Download the key file
- Go to Google Sheets API and Enable it for your project
- Optionally enable the Google Drive API too
- Create a new Google Sheet
- Share it with your Service Account email (e.g.
inventory@your-project.iam.gserviceaccount.com) as Editor - Copy the Sheet ID from the URL:
https://docs.google.com/spreadsheets/d/<SHEET_ID>/edit
Create three tabs in your sheet:
Events tab — row 1 headers:
| A | B | C | D | E | F | G | H | I | J |
|---|---|---|---|---|---|---|---|---|---|
| Timestamp | Method | Path | Query | Request Headers | Request Body | Status | Latency (ms) | Client IP | Country |
Inventory tab — row 1 headers:
| A | B | C | D | E |
|---|---|---|---|---|
| Timestamp | Method | Path | Status | Latency (ms) |
Inventory_Clean tab — paste this formula in cell A1:
=QUERY(Inventory!A:E,"SELECT Col2, Col3, MIN(Col1), MAX(Col1), COUNT(Col1) WHERE Col2 IS NOT NULL GROUP BY Col2, Col3 ORDER BY COUNT(Col1) DESC LABEL Col2 'Method', Col3 'Path', MIN(Col1) 'First Seen', MAX(Col1) 'Last Seen', COUNT(Col1) 'Request Count'",1)
For status codes seen per endpoint, add this in cell F2 and drag down:
=IFERROR(TEXTJOIN(", ",TRUE,UNIQUE(FILTER(TEXT(Inventory!$D$2:$D,"0"),(Inventory!$B$2:$B=A2)*(Inventory!$C$2:$C=B2)*(Inventory!$D$2:$D<>"")))))
Edit wrangler.toml and update the upstream URL if needed:
name = "gateless"
main = "src/index.js"
compatibility_date = "2024-01-01"In src/index.js, update the upstream origin on this line:
`https://your-api-origin.example.com${url.pathname}${url.search}`# Paste the full contents of your downloaded JSON key file
wrangler secret put GOOGLE_SERVICE_ACCOUNT_JSON
# Your Google Sheet ID
wrangler secret put SHEET_ID
# A strong random key for the /__inventory admin redirect
wrangler secret put ADMIN_KEYwrangler deployOption A — Custom Domain (recommended):
Cloudflare Dashboard → Workers & Pages → your worker
→ Settings → Domains & Routes → Add Custom Domain
→ Domain: your-api.example.com
Option B — Workers Route:
Cloudflare Dashboard → Websites → your-domain.com
→ Workers Routes → Add Route
→ Route: your-api.example.com/*
→ Worker: gateless
Set your origin in src/index.js:
const upstream = new Request(
`https://your-real-origin.example.com${url.pathname}${url.search}`,
...
);Add paths to skip logging (health checks, static assets, etc.):
const SKIP_PATHS = new Set(["/", "/favicon.ico", "/health", "/ping", "/robots.txt"]);Headers that are always replaced with [REDACTED] in logs:
const REDACTED_HEADERS = new Set([
"authorization", "cookie", "x-api-key",
"x-client-secret", "x-auth-token", "x-client-key",
]);JSON body fields that are recursively redacted:
const REDACTED_FIELDS = new Set([
"password", "pin", "cvv", "card_number", "account_number",
"otp", "secret", "token", "private_key", "seed_phrase",
]);Add any domain-specific sensitive field names here.
Visit /__inventory?key=<ADMIN_KEY> on your worker domain to be redirected directly to your Google Sheet.
| Service | Free Limit | Typical Usage |
|---|---|---|
| Cloudflare Workers | 100K requests/day | API proxy layer |
| Cloudflare Workers CPU | 10ms/request | Well within limit |
| Google Sheets API | 300 requests/min | 2 writes per API call |
| Google Sheets storage | 10M cells | ~500K requests before limits |
For high-traffic APIs (>50K requests/day), consider batching writes or routing to a heavier backend.
- Never commit your
GOOGLE_SERVICE_ACCOUNT_JSONor any secrets to the repo — always usewrangler secret put - The Service Account only needs
spreadsheetsscope — it cannot access other Google services - The
ADMIN_KEYfor/__inventoryshould be a long random string (openssl rand -hex 32) - Response bodies are intentionally not logged — they often contain sensitive customer data
- The worker adds no new attack surface to your origin — it only forwards the original request
gateless/
├── src/
│ └── index.js # Worker source — proxy + logging logic
├── wrangler.toml # Cloudflare Worker config
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
└── README.md
1. Request arrives at Cloudflare edge
2. Worker clones the request body (stream can only be read once)
3. Forwards the original request to origin — response returned to client immediately
4. ctx.waitUntil() runs two async Sheets appends after the response is sent:
a. appendEventRow → full request detail to Events tab
b. appendInventoryHit → lightweight hit to Inventory tab
5. Inventory_Clean tab auto-aggregates via QUERY formula — no worker logic needed
Authentication to Google Sheets uses a Service Account JWT signed with the Web Crypto API (no external dependencies) and exchanged for a short-lived OAuth2 access token on each request.
PRs welcome. Common improvements people have asked for:
- Token caching via Durable Objects (reduces OAuth round-trips at high traffic)
- Batch writes to reduce Sheets API quota usage
- Support for additional storage backends (D1, R2, Supabase)
- Request sampling for high-traffic deployments
- A dashboard UI served from
/__inventory/ui
This project is licensed under the MIT License - see the LICENSE file for details.