DecisionsPlatform
ADR-0011: The API runs as Vercel functions inside the web project (staging first)
Page actions
- View as Markdown (opens in a new tab)
- Ask ChatGPT (opens in a new tab)
- Ask Claude (opens in a new tab)
ChatGPT and Claude are third-party services. The link gives them this page's public address and a short instruction. This site records nothing.
Preview: the published page may be older or missing.
Why the API runs as Vercel functions inside the web project on staging, and how production will follow.
https://docs.gurbanisoul.com/adr/0011-api-as-functions-in-the-web-project/
Status: accepted (2026-09-25) — live on staging; production moves onto all (the whole API as one
function) in the release after 1.3.9, with the Render single API kept deployed as its rollback target. Amended 2026-09-26: see Amendments. Supersedes the hosting part of ADR-0010 (per-context Render services); its
slicing, generated routing and proofs stand.
Context
Running the five contexts as always-on Render services in production would cost about
US$35 a month; free Render instances sleep and share a 750-hour monthly cap, so they cannot serve
production. The web is already on Vercel Pro, which runs Python functions on usage-based pricing out
of the plan’s monthly credit. The API is stdlib Python over a read-only SQLite file, and serve.H is
a BaseHTTPRequestHandler — the handler shape Vercel’s Python runtime loads directly.
A trial as a separate Vercel project (sggs-api) passed the whole golden contract but showed two
things: Vercel’s platform flood protection answered one burst-tested host with a browser challenge
(which an API client cannot pass), and a project without a framework publishes every non-function
file — the databases included — as static files. A second project would also put a second edge and
firewall between the web and the API.
Decision
- Functions in the web project. At deploy time
tools/build_api_functions.pygenerates, underfrontend/: one function per context (api/svc/<ctx>.py) andall(the whole API on the full database), each fixingSGGS_MODULES,SGGS_DBand the commit, refusing to load when its slice lacks a declared table, and serving withserve.Hunchanged. Slices are cut and proven bytools/slice_db.py. Nothing generated is committed. Every CI deployment carries all six functions. - Routing stays generated from the code.
tools/gen_gateway.pywritesfrontend/vercel.json’s rewrites; the function configuration (bundles, region) is added at deploy time together with the generated files, so a build without them — a Vercel Git preview — still succeeds. Per environment (gateway/routes.json),api_platform: vercelrewrites each routed context’s prefixes (and/api/v1twins) to its function and everything else toall;api_platform: renderproxies/apito the Render single API. Rewrites are internal (one edge, one firewall), use unnamed groups so the handler sees the original path and query, and are host-conditioned for staging. - What ships is proven before it deploys. After
vercel build,--verify-outputreads every function’s bundled file map: its entry, the API code and its own database only — anything else from the web, or another context’s database, fails the build — and no database or API source may appear in the static output. After deploy,scripts/ci/verify_functions.pychecks every function’s/readyz(commit, contexts,X-Service) and that the API’s files are not downloadable; the gateway probes and the whole golden contract (266 records) replay through the staging site. - Region
pdx1, next to the Render API it replaces, so latency compares like for like.
Consequences
Staging no longer uses Render; the sggs-staging-* services, sggs-api-staging
and the RENDER_API_KEY / RENDER_DEPLOY_HOOK_STAGING secrets can be removed. Web and API deploy
atomically (no version skew; one rollback). Functions scale to zero after a few idle minutes, so the
first request after a quiet spell pays a cold start (about a second in the trial); the p95 budget
excludes cold starts, as ADR-0010’s does. Vercel runs Python 3.12 with SQLite 3.40.0 (FTS5, trigram,
bm25) — the Render image ran 3.40.1 — and the contract passed byte-for-byte on it. Moving production
is a change of production.api_platform to vercel, released through the normal pipeline and gated
on the performance baseline and the data canary (docs/process/runbooks/services-production.md).
Amendments
- 2026-09-26 — production’s contexts move in one release. Production went onto
allwith 1.3.10. Its per-context routing (services-production step 3) was merged and then held back (owner decision 2026-10-11:production.servicesingateway/routes.jsonis empty) and goes live in one release for all five, a later one after the app is live on the App Store (1.4.0 keepsall): under the one-version policy every release is also an App Store update (owner decision). Each context is still gated on its own —verify_functions --env production, the golden contract before promotion, the performance gate per context — and rolls back alone by leaving the list. The Render single API stays deployed as the rollback until services-production step 4. The decision above is unchanged.
Sri Guru Granth Sahib Ji — Knowledge Base wiki. Generated from commit c3d9360. Updated 13 October 2026. This copy may be out of date; the page is at https://docs.gurbanisoul.com/adr/0011-api-as-functions-in-the-web-project/