Local Cloudflare Worker implementation for the key-required Patcher Public Open API at https://api.patcher.xyz/v1.
The MVP is live at api.patcher.xyz. The database foundation, Vault pepper, least-privilege reader login, direct-endpoint Hyperdrive binding, Durable Object, Worker secret, custom domain, and production Worker are active. Catalogue, key lifecycle, quota-reporting, ETag, HEAD, and cache behavior passed production smoke tests, and the temporary smoke Worker has been deleted. The owner deferred an outer WAF rule for the initial launch; mandatory API keys and per-key Durable Object quotas remain active. The production User Area flag is committed on develop and awaits the next app release.
- Public consumer docs: docs.patcher.xyz/reference/public-open-api
- Canonical API contract:
openapi.yaml - Canonical API contract on GitHub:
Polyterative/Patcher/cloudflare/public-api/openapi.yaml - Base URL:
https://api.patcher.xyz/v1 - Format: JSON only,
GET/HEADonly, CORS*for read requests. - Authentication: every data request must send an
Authorizationheader using theBearerscheme. The credential must start withpk_live_and then contain exactly 22 base64url characters. - Default free quota:
5,000requests/month and60requests/minute. Partner keys are manually provisioned. - License for exposed catalogue data: CC BY 4.0; API consumers must attribute Patcher when redisplaying or reusing the data.
No anonymous tier exists. Missing or malformed keys fail before cache lookup.
Consumer
-> Cloudflare Worker route api.patcher.xyz/v1/*
-> URL normalization + route/query allowlist
-> API key HMAC verification with API_KEY_PEPPER
-> Hyperdrive -> direct Supabase Postgres endpoint -> public.verify_api_key(...)
-> per-key Durable Object API_KEY_COUNTER quota consume
-> Cloudflare Cache API lookup by normalized public URL only
-> on cache miss: Hyperdrive -> direct Supabase Postgres endpoint -> api_v1_* views
The Worker never contains a Supabase service_role key, project JWT signing secret, user credential, or raw API key storage. Runtime reads use a least-privilege api_reader database role through Hyperdrive.
- Reject any method other than
GET,HEAD, orOPTIONSwith405 method_not_allowedand anAllowheader, before path/query normalization or authentication. - Normalize path and query parameters. Unknown query parameters return
400 unknown_parameter; unsupportedqreturns400 unsupported_parameter. - Parse the
Authorizationheader. Missing returns401 missing_authorization; a non-Bearerscheme, missingpk_live_prefix, wrong suffix length, or non-base64url suffix returns401 malformed_authorization. - HMAC the decoded 16-byte key suffix with
API_KEY_PEPPER; verify active metadata throughpublic.verify_api_key(bytea). - Consume quota in the per-key Durable Object before cache lookup. Quota responses attach
X-RateLimit-Limit-Minute,X-RateLimit-Remaining-Minute,X-RateLimit-Limit-Month,X-RateLimit-Remaining-Month, andX-RateLimit-Reset; in the MVP,X-RateLimit-Resetis the current minute-window start timestamp, not the next reset time.429also includesRetry-After. - Serve from Cache API when possible. Cache keys use only method, normalized path, and normalized query;
Authorizationis never part of the cache key orVary. - On miss, query only
api_v1_*views through parameterized postgres.js calls over Hyperdrive. - Return SHA-256
ETagvalues. MatchingIf-None-Matchreturns304after authentication and quota consumption, with current per-key quota headers.
| Endpoint | Purpose | Notes |
|---|---|---|
GET /v1/modules |
Paginated public modules | Filters: manufacturer_id, hp, standard, tag; includes: ins, outs, tags, panels. |
GET /v1/modules/{id} |
One public module | Supports fields and module includes. |
GET /v1/manufacturers |
Manufacturers that have at least one publishable module | Supports pagination and fields. |
GET /v1/manufacturers/{id} |
One public manufacturer | include=modules adds safe module summaries. |
GET /v1/standards |
Reference standards | Supports pagination and fields. |
GET /v1/tags |
Reference tags | type is a semantic lowercase enum or null; supports pagination and fields. |
Module, manufacturer, and tag IDs are positive integers. Standard IDs are nonnegative because production standard 0 is the valid 3U row; the module standard filter therefore accepts 0.
List endpoints support:
limit:1..100, default50.sort:nameorid, ascending, withidas tie-breaker forname.cursor: opaque base64url cursor shaped as{"v":1,"s":<last_sort_value>,"id":<last_id>}.fields: comma-separated allowlisted top-level fields;idis always retained.q: reserved for futurepg_trgmsearch and intentionally returns400 unsupported_parameterin the MVP.
Use a placeholder key shape in docs and tests only. Real keys are returned once by the database mint RPC and must not be committed, logged, or pasted into issue trackers.
AUTH_HEADER='Authorization'
AUTH_SCHEME='Bearer'
API_KEY='pk_live_<replace-with-real-22-char-suffix>'
curl -sS 'https://api.patcher.xyz/v1/modules?limit=10&include=tags,panels' \
-H "${AUTH_HEADER}: ${AUTH_SCHEME} ${API_KEY}"AUTH_HEADER='Authorization'
AUTH_SCHEME='Bearer'
API_KEY='pk_live_<replace-with-real-22-char-suffix>'
curl -i 'https://api.patcher.xyz/v1/manufacturers/123?include=modules&fields=name,website_url' \
-H "${AUTH_HEADER}: ${AUTH_SCHEME} ${API_KEY}"AUTH_HEADER='Authorization'
AUTH_SCHEME='Bearer'
API_KEY='pk_live_<replace-with-real-22-char-suffix>'
curl -i 'https://api.patcher.xyz/v1/tags' \
-H "${AUTH_HEADER}: ${AUTH_SCHEME} ${API_KEY}" \
-H 'If-None-Match: "previous-etag"'Error envelope:
{
"error": {
"code": "invalid_key",
"message": "API key is invalid",
"request_id": "00000000-0000-4000-8000-000000000000"
}
}The v1 contract exposes only allowlisted catalogue fields from publishable module/manufacturer data and reference rows. It deliberately excludes:
- private users, private profiles, private racks, private patches, addresses, marketplace transactions, emails, admin IDs, tokens, and analytics;
- submitter attribution and moderation/private operational fields;
- panel image URLs and panel filenames;
- pricing and store listings;
- bulk export endpoints;
- public rack and public patch endpoints.
Bulk JSONL export is a Structural follow-up. Public racks and patches are deferred to a later contract review and will use existing opaque public_id patterns rather than integer IDs.
| Path | Responsibility |
|---|---|
src/index.ts |
Top-level Worker pipeline, auth/quota/catalogue handoff, Cloudflare export. |
src/auth.ts |
Bearer / pk_live_ parsing and HMAC digest generation. |
src/api-key-metadata-cache.ts |
Fixed-TTL isolate metadata cache; revocation bound stays <= 60 seconds. |
src/api-key-counter.ts |
Durable Object quota counter, usage flush, alarm retry. |
src/quota.ts and src/quota-response.ts |
Pure quota boundary logic and rate-limit headers. |
src/request.ts |
Route detection, query allowlists, normalization, cursors. |
src/catalogue-provider.ts |
Parameterized catalogue queries against api_v1_* views. |
src/catalogue-serving.ts |
Cache API, ETag/304, stale-while-revalidate, origin error handling. |
src/catalogue-mapping.ts |
Row normalization, field allowlists, sparse fields, cursor encoding. |
src/database.ts |
postgres.js Hyperdrive client, API key RPC calls, usage reporter. |
wrangler.jsonc |
Local Worker entry and Durable Object class declaration only; no real remote IDs. |
openapi.yaml |
OpenAPI 3.1 consumer contract. |
../../supabase/migrations/20260724133100_api_reader_roles.sql |
Local role foundation, credential-free. |
../../supabase/migrations/20260724133200_api_identity.sql |
API tiers, keys, usage, Vault-backed mint/verify/revoke/report RPCs. |
../../supabase/migrations/20260724133300_api_v1_views.sql |
Security-barrier public catalogue views and least-privilege grants. |
../../supabase/migrations/20260724133400_api_vault_permissions.sql |
Restricted Vault key-ID access required by the managed migration owner. |
../../scripts/tests/public-api-worker.test.mjs |
Local Worker contract tests. |
../../scripts/tests/public-open-api-migrations.test.cjs |
Static migration contract tests. |
Use pnpm; do not use npm or watch-mode test commands.
pnpm test:functions:public-api-worker
pnpm test:functions:public-open-api-migrations
node scripts/checks/check-docs.cjsNo-dependency OpenAPI smoke check:
node -e "const fs=require('node:fs'); const s=fs.readFileSync('cloudflare/public-api/openapi.yaml','utf8'); for (const t of ['openapi: 3.1.0','/modules:','/manufacturers:','apiKey:','ErrorResponse:']) if (!s.includes(t)) throw new Error('OpenAPI smoke check missing '+t);"The checked-in wrangler.jsonc intentionally contains no production resource IDs or credentials. Use dependency-injected tests for local validation; operators deploy only through approved environment-specific configuration that remains outside git.
- No write API.
- No anonymous access.
- No GraphQL.
- No billing or paid self-service tier in v1.
- No direct PostgREST/
anonexposure. - No
service_role, JWT signing secret, or raw database credential in the Worker. - No Patcher-docs repository edits from this package; public marketing/consumer docs live at https://docs.patcher.xyz/reference/public-open-api.