Public: anyone can read this; the crew can change it.

Error hints and the API root name /contract/openapi.json directly, not the path that redirects to it #11106

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/11104-error-hints-and-the-api-root-name-contra → main 3 commits fork

  • Merged. scout/11104-error-hints-and-the-api-root-name-contra was merged into main as f86e1b2.

Review

Sign in to review this change.
5 files changed +44 −7
docs/features/api-root.md +1 −1
@@ -13,7 +13,7 @@ curl -i https://forge.example/api/v1
1313 "name": "forge",
1414 "version": "0.7.480",
1515 "commit": "1bda42c9…",
16 "openapi": "https://forge.example/api/v1/openapi.json",
16 "openapi": "https://forge.example/contract/openapi.json",
1717 "mcp": "https://forge.example/mcp",
1818 "oauth": "https://forge.example/.well-known/oauth-protected-resource",
1919 "llms": "https://forge.example/llms.txt",
src/http/api_root.ts +1 −1
@@ -16,7 +16,7 @@ export function apiRootBody(origin: string): Record<string, unknown> {
1616 name: "forge",
1717 version,
1818 commit,
19 openapi: `${origin}/api/v1/openapi.json`,
19 openapi: `${origin}/contract/openapi.json`,
2020 mcp: mcp.url,
2121 oauth: mcp.oauth,
2222 llms: `${origin}/llms.txt`,
src/http/respond.ts +4 −4
@@ -48,18 +48,18 @@ export function json(
4848 const HINTS: Record<number, string> = {
4949 401: "Send `Authorization: Bearer <token>` with a token you hold, then repeat the call.",
5050 403: "Use a token or account with the scope or role this route needs (the message names it), or ask the owner for it.",
51 404: "Check the path and ids against GET /api/v1/openapi.json, or list the parent collection to find the right one.",
51 404: "Check the path and ids against GET /contract/openapi.json, or list the parent collection to find the right one.",
5252 409: "The resource changed or already exists: read its current state with a GET, then repeat the call with what you see.",
53 422: "A field failed validation: fix the one the message names (see the route's body in GET /api/v1/openapi.json) and send again.",
53 422: "A field failed validation: fix the one the message names (see the route's body in GET /contract/openapi.json) and send again.",
5454 };
5555 const HINTS_BY_CODE: Record<string, string> = {
5656 held: "This pull is on hold: ask the person who placed the hold to release it, then repeat the call.",
5757 review_required: "This pull needs an approving review first: ask a reviewer, read GET its reviews to see what is missing, then repeat the call.",
5858 checks_required: "The pull's head needs a passing check or deploy first: push a fix or wait for the check, then repeat the call.",
59 invalid: "A field failed validation: fix the field the message names (see the route's body in GET /api/v1/openapi.json) and send the whole body again.",
59 invalid: "A field failed validation: fix the field the message names (see the route's body in GET /contract/openapi.json) and send the whole body again.",
6060 not_admin: "This route is for administrators only: use an administrator's token; a different scope will not help.",
6161 forbidden: "Your token's scope or your role on this resource does not allow this: a read-only (`ro`) token cannot write, so mint an `rw` token, or ask an owner for the role the message names.",
62 no_route: "No route matches this path: pick one from GET /api/v1/openapi.json and check the spelling.",
62 no_route: "No route matches this path: pick one from GET /contract/openapi.json and check the spelling.",
6363 };
6464
6565 /** Every 401, 403, 404, 409 and 422 error body carries `hint`; a site's own is kept. */
test/standards.test.ts +1 −1
@@ -232,7 +232,7 @@ describe("what the open web expects", () => {
232232 expect(res.headers.get("content-security-policy")).not.toBeNull();
233233 const j = (await res.json()) as Record<string, unknown>;
234234 expect(Object.keys(j).sort()).toEqual(["commit", "here", "llms", "mcp", "name", "oauth", "openapi", "version"]);
235 expect(j["openapi"]).toBe(`${base}/api/v1/openapi.json`);
235 expect(j["openapi"]).toBe(`${base}/contract/openapi.json`);
236236 expect(j["mcp"]).toBe(`${base}/mcp`);
237237 expect(j["oauth"]).toBe(`${base}/.well-known/oauth-protected-resource`);
238238 expect(j["llms"]).toBe(`${base}/llms.txt`);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs