# ADR-0011: The API runs as Vercel functions inside the web project (staging first) Why the API runs as Vercel functions inside the web project on staging, and how production will follow. - Page: - Updated: 2026-10-13 - From the Sri Guru Granth Sahib Ji — Knowledge Base wiki. Sri Guru Granth Sahib Ji is quoted verbatim, cited by Ang, and never altered. --- **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.py` generates, under `frontend/`: one function per context (`api/svc/.py`) and `all` (the whole API on the full database), each fixing `SGGS_MODULES`, `SGGS_DB` and the commit, refusing to load when its slice lacks a declared table, and serving with `serve.H` unchanged. Slices are cut and proven by `tools/slice_db.py`. Nothing generated is committed. Every CI deployment carries all six functions. - **Routing stays generated from the code.** `tools/gen_gateway.py` writes `frontend/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: vercel` rewrites each routed context's prefixes (and `/api/v1` twins) to its function and everything else to `all`; `api_platform: render` proxies `/api` to 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-output` reads 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.py` checks 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 `all` with 1.3.10. Its per-context routing (services-production step 3) was merged and then held back (owner decision 2026-10-11: `production.services` in `gateway/routes.json` is 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 keeps `all`): 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.