Moves `api.texttocad.dev` out of the docs site into its own Vercel
project, `apps/api` (`texttocad-api`), deployed on every merge to `main`
that touches it. Its version feed now reads PyPI. The receiver can then
go live before any release sends a new batch schema (#586's schema 4)
instead of after it. The wire contract every released cadgen (0.7.7 on)
uses is unchanged.
## What changes
- **`apps/api`**: one zero-dependency Vercel Function (`api/v1.js`,
web-standard `GET`/`POST`) serving every `/v1/*` path through one
`vercel.json` rewrite. The receiver's code (`handler`, `events`,
`posthog`, tests, the Neon migration script) moves here unchanged: the
first commit is a pure `git mv`. `public/` holds only `robots.txt`, so
Vercel serves the function and nothing else. `apps/api` isn't an npm
workspace, so the root lockfile and `npm ci` are untouched.
- **Version feed from PyPI**: `GET /v1/versions` answers `{"latest":
...}` as before, but `latest` is the newest strict `X.Y.Z` release with
an unyanked file on PyPI's simple index (PEP 691 JSON), the index uv
resolves pins through. It fetches with a 3 s timeout and keeps what it
read for 5 minutes per instance, serving that while PyPI fails. Replies
carry `s-maxage=3600, stale-while-revalidate, stale-if-error`. With
nothing read and PyPI down it answers 503, which a released cadgen
treats as a failed daily check: silently, and it tries again the next
day. The feed now moves the moment PyPI lists a release, so a release no
longer deploys anything for the API.
- **Docs app**: `app/v1` and the API code are gone; `deploy-docs.yml` no
longer health-checks api.texttocad.dev. A release still deploys docs,
for the site's header version.
- **CI**: a new `api` job (`scripts/test/test-api.sh`, about 20–30 s),
selected only by `apps/api/**`. It is not a required check.
`deploy-api.yml` runs the API tests, then `deploy-vercel-app.sh` against
`VERCEL_API_PROJECT_ID`, then checks `/v1/health` and `/v1/versions`.
- **Docs**: `apps/api/README.md` takes the API sections from
`apps/docs/README.md`. AGENTS.md, CONTRIBUTING.md and the release
workflow's comments now say the docs deploy no longer moves the feed.
The privacy policy stays true as written.
## Already set up
- Vercel project `texttocad-api`: Root Directory `apps/api`, functions
in `iad1`, previews behind Vercel Authentication. The four PostHog
settings are added (Production, Sensitive).
- GitHub secret `VERCEL_API_PROJECT_ID`.
## Cutover, in order (after merge)
1. This merge runs `deploy-api.yml`. Until step 3 its URL checks are
answered by the docs project, so they pass without testing the new
project; step 2 tests it.
2. Check the new deployment's `*.vercel.app` address: `/v1/health` 200
(PostHog settings), `/v1/versions`, one test batch, and a `/v1/forget`
of that test install.
3. Move `api.texttocad.dev` from the docs project to `texttocad-api` and
check it live.
4. **No docs deploy (manual or a release) before step 3**: the merged
docs app no longer serves `/v1`.
5. Rebase #586 onto this. Every rename auto-merges; two small conflicts
(`handler.test.mjs` imports, and #586's README edits moving to
`apps/api/README.md`).
## Behaviour differences cadgen doesn't use
- A trailing slash is served directly instead of a 308 redirect.
- Methods other than GET and POST get Vercel's default answer instead of
Next's 405, auto-OPTIONS and auto-HEAD.
- `www.texttocad.dev/v1` no longer reaches the API.
## Tests
- API tests: 27/27.
- `scripts/test/test-global.sh`: 210 tests OK.
- `npm --prefix apps/docs run check` passes, and the Next route list has
no `/v1`.
- `check-version.sh`, `sync-version.mjs --check`, `check-builds.sh
--tree-only` and actionlint pass.
- A local `vercel@50.28.0 build` produced one function, `api/v1.func`
(nodejs24.x), with route `^/v1(?:/(.*))$` → `/api/v1`.
- Calling `api/v1.js` locally with test requests: health and versions
200 (with or without a trailing slash), events 204, Origin 403,
text/plain 415, forget 204, unknown path 404.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>