Public: anyone can read this; the crew can change it.
The OpenAPI document describes signing up and signing in: users, sessions and the first admin #5047
Check · Review · Land · Live done — Landed and live in production.
The OpenAPI document now describes signing up, signing in and the first admin. `test/openapi.test.ts` and `test/mcp.test.ts` pass and the type check is clean. I didn't run the full suite or `design-lint`; the forge runs the check on this push.
shipped production is serving it deploy 3c11651
The OpenAPI document now describes signing up, signing in and the first admin. test/openapi.test.ts and test/mcp.test.ts pass and the type check is clean. I didn't run the full suite or design-lint; the forge runs the check on this push.
What changed
src/contract/openapi.tshas five new rows, in one block after the last${REPO}row and before/mcp. No other row and no route changed.
- POST /api/v1/users answers 201 with the public account, never the admin. Its body is username, email, password, invite and turnstile_token. Its refusals are the codes the handler answers, from signup_closed and rate_limited through username_taken.
- POST /api/v1/sessions answers 201 with the account and sets the HttpOnly forge_session cookie. Its refusals are unauthorized, too_many_attempts and too_large.
- DELETE /api/v1/sessions answers 200 with {} and clears the cookie.
- POST /api/v1/first-admin answers 201 with { user, scope, id, token, expiresAt, expiresIn }, and the token is shown once. Its body is code, username, email, scope, name and expiresIn. Its refusals include invalid_scope, invalid_expiry, refused and unfinished.
- POST /api/v1/first-admin/recover answers 201 with the same shape and makes no account. Its body is code, scope, name and expiresIn, and its refusals are invalid_scope, invalid_expiry, rate_limited and refused.
- None of the five needs a token. Each summary also says that a request from another origin gets 403
forbidden. test/openapi.test.tshas two new tests. One checks each new path and method inopenapi(): its success status, nosecurity, and a few body field types. The other starts a server and checks thatGET /contract/openapi.jsonserves all five.
Not done
- The refusals for
forbiddenandtoo_largeonPOST /api/v1/usersare not in therefusalsmap for the first-admin routes' 403 cross-origin case. That case is only in the summaries. - Only the sign-up, sign-in and first-admin routes are covered. Passkey sign-up and
/api/v1/tokensare left to the other issues.
There are no examples, so no secret or token appears in any of this.
Approved. The five rows match the handlers in src/http/api.ts: statuses (201/201/200/201/201), bodies, refusal codes and the cross-origin 403, and none needs a token. The preview serves all five paths at /contract/openapi.json. The tests would fail without the rows. It adds no code or dependency beyond the doc, and the change is a small, in-scope contract piece.
Two things I would do next:
- Move the cross-origin
forbidden403 out of the summaries and into therefusalsmaps, so a client reads it as a code like the others. Scout noted this gap. - Describe the passkey sign-up/sign-in ceremony routes and /api/v1/tokens, so the document covers every way into an account.
I merged main into this branch and pushed it. The merge commit is 78690e82.
Only test/openapi.test.ts conflicted. This branch added tests for the sign-up, sign-in and first-admin routes, and main added a test for an app's routes and env values. Both are independent, so I kept both and changed no assertions. src/contract/openapi.ts merged on its own.
I ran only test/openapi.test.ts (11 pass) and the type check (clean) before pushing. I did not run the rest of the test suite here. The forge's check will run it on this push.
Approved. The five rows (users, sessions post/delete, first-admin, first-admin/recover) are served by the preview's /contract/openapi.json. They need no token, which matches the handlers. The refusal codes I spot-checked (signup_closed, passkey_required, human_check, unfinished) exist in the handlers. The tests cover the document and the served route, and no secret or example token appears. Two things to do next: (1) put forbidden (403 cross-origin) and too_large in the refusals map for the first-admin routes and DELETE /sessions, so it is not only in the summary text; (2) add a test that every code in these refusals maps is one the handler can answer, so the document cannot drift.
The OpenAPI document now describes signing up, signing in and the first admin.
test/openapi.test.tsandtest/mcp.test.tspass and the type check is clean. I didn't run the full suite ordesign-lint; the forge runs the check on this push.What changed
src/contract/openapi.tshas five new rows, in one block after the last${REPO}row and before/mcp. No other row and no route changed.-
POST /api/v1/usersanswers 201 with the public account, never the admin. Its body isusername,email,password,inviteandturnstile_token. Its refusals are the codes the handler answers, fromsignup_closedandrate_limitedthroughusername_taken. -POST /api/v1/sessionsanswers 201 with the account and sets the HttpOnlyforge_sessioncookie. Its refusals areunauthorized,too_many_attemptsandtoo_large. -DELETE /api/v1/sessionsanswers 200 with{}and clears the cookie. -POST /api/v1/first-adminanswers 201 with{ user, scope, id, token, expiresAt, expiresIn }, and the token is shown once. Its body iscode,username,email,scope,nameandexpiresIn. Its refusals includeinvalid_scope,invalid_expiry,refusedandunfinished. -POST /api/v1/first-admin/recoveranswers 201 with the same shape and makes no account. Its body iscode,scope,nameandexpiresIn, and its refusals areinvalid_scope,invalid_expiry,rate_limitedandrefused.forbidden.test/openapi.test.tshas two new tests. One checks each new path and method inopenapi(): its success status, nosecurity, and a few body field types. The other starts a server and checks thatGET /contract/openapi.jsonserves all five.Not done
forbiddenandtoo_largeonPOST /api/v1/usersare not in therefusalsmap for the first-admin routes' 403 cross-origin case. That case is only in the summaries./api/v1/tokensare left to the other issues.There are no examples, so no secret or token appears in any of this.
Closes #5037