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

GET /api/v1/openapi.json answers the OpenAPI document, the same as /contract/openapi.json #4159

Merged opened this

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

Merged

scout/forge:scout/4149-get-api-v1-openapi-json-answers-the-open → main 1 commit fork

  • Merged. scout/4149-get-api-v1-openapi-json-answers-the-open was merged into main as 54c9119.

Review

Sign in to review this change.
3 files changed +22 −0
src/http/api.ts +13
@@ -5796,6 +5796,19 @@ export function apiRoutes(router: Router<Handler>): void {
57965796 return json(readFleetGraph(ctx.app, ctx.auth.user, asked.value));
57975797 });
57985798
5799 // The address an agent tries first.
5800 router.add(
5801 "GET",
5802 "/api/v1/openapi.json",
5803 () =>
5804 new Response(null, {
5805 status: 308,
5806 headers: {
5807 location: "/contract/openapi.json",
5808 "cache-control": "public, max-age=86400",
5809 },
5810 }),
5811 );
57995812 // What the caller can do on a forge page, as who they are (#604): the
58005813 // manifest every page links to. It differs by caller, so it is never cached.
58015814 router.add("GET", "/api/v1/here", (ctx) => {
src/web/standards.ts +1
@@ -283,6 +283,7 @@ export function llmsTxt(ctx: Ctx): Response {
283283 "",
284284 `- MCP: ${o}/mcp (OAuth discovery: ${o}/.well-known/oauth-protected-resource)`,
285285 `- API: ${o}/api/v1, Authorization: Bearer <token>`,
286 `- OpenAPI: GET ${o}/api/v1/openapi.json (redirects to ${o}/contract/openapi.json)`,
286287 "",
287288 "## Public repositories",
288289 "",
test/mcp.test.ts +8
@@ -1140,6 +1140,14 @@ describe("the forge over MCP", () => {
11401140 expect(doc.openapi).toBe("3.1.0");
11411141 expect(doc.paths["/mcp"].post).toBeDefined();
11421142 });
1143 test("GET /api/v1/openapi.json redirects to the document", async () => {
1144 const res = await fetch(`${base}/api/v1/openapi.json`, { redirect: "manual" });
1145 expect(res.status).toBe(308);
1146 expect(res.headers.get("location")).toBe("/contract/openapi.json");
1147 expect(res.headers.get("cache-control")).toContain("public");
1148 const target = await fetch(new URL(res.headers.get("location")!, base));
1149 expect(target.status).toBe(200);
1150 });
11431151 test("feed_chips reads and switches the chips the feed answers through", async () => {
11441152 app.bus.append({ kind: "app.serving", repoId: game.id, actorId: alice.id, payload: {} });
11451153 const kinds = async () => {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs