ADR 0009: Serve a GitHub-shaped API, not a gh-compatible one
Date: 2026-08-25
Status: Accepted
Context
The API moved from /api/v3 to /api/v1 (issue #211). The version in the path
names this API's own version, and this API is at its first. One deploy serves
both paths: OpenAgentsWeb.Plugs.ApiV3Rewrite rewrites /api/v3/* to
/api/v1/* so that clients released against the old prefix keep working while
the fleet upgrades.
That alias raised a question the rename audit
(docs/2026-08-24-api-v1-rename-audit.md) left open, and issue #215 asks you
to settle: GitHub's own CLI hardcodes https://HOST/api/v3/ for any host other
than github.com, with no way to override the path segment. If gh is a client
this forge wants, the alias can never be deleted.
The audit stopped at the path. Reading the path alone suggests that keeping
/api/v3 buys gh compatibility for the price of one plug. Running gh
against the live application says otherwise. Every row below is gh 2.89.0
against openagents.com on 2026-08-25, captured with GH_DEBUG=api:
| Command | Request gh makes |
Result |
|---|---|---|
gh issue list -R … |
POST /api/graphql |
404 |
gh issue view 215 -R … |
GET /api/v3/meta |
406 |
gh api repos/OWNER/REPO/issues |
GET /api/v3/repos/…/issues |
200 |
gh api https://openagents.com/api/v1/repos/OWNER/REPO/issues |
GET /api/v1/repos/…/issues |
200 |
Three facts follow.
- The ported commands do not use REST.
gh issue listsends a GraphQL query to/api/graphql, andgh issue viewfirst probesGET /api/v3/meta. This application serves neither endpoint, so no alias makes those commands work. - The one
ghsurface the alias keeps alive isgh api, the raw REST passthrough, which is acurlsubstitute rather than a reason to usegh. - Even
gh apidoes not need the alias. Given a full URL it calls/api/v1directly and answers200. Only a bare path picks up the/api/v3prefix.
Decision
Serve a GitHub-shaped API, and do not claim gh compatibility.
ghis not a supported client. The supported clients are theopenagentsCLI, which is first-class, and any GitHub-shaped client that takes an explicit base URL, such as Octokit configured withbaseUrl: "https://openagents.com/api/v1"./api/v3stays a dated migration alias for released clients, and it gets deleted on the schedule issue #216 describes. Nothing aboutghmakes it permanent.- Every versioned route is declared at
/api/v1, and every URL a response emits names/api/v1.OpenAgentsWeb.Plugs.ApiV3Rewriteis the only place inlib/that names the old prefix, so removing the alias stays a one-file change.INVARIANTS.mdrecords this as FORGEAPI-002, andtest/openagents_web/api_version_posture_test.exsproves it.
Consequences
- Issue #216 proceeds. The gh-posture decision it waited on does not make the alias permanent.
- Documentation states the limit rather than implying drop-in tooling.
priv/docs/rest-api.mdnamesghas unsupported and gives you the two clients that work. The episode 273 summary indocs/episode-triage.mdcarries a dated correction, because "soghand octokit work unchanged" recorded an intent that the evidence does not support. - A response that emits an
/api/v3URL is now a build failure. The trace ingest route landed during the rename and returned aurlfield naming/api/v3/traces/{id}, a link that would have gone dead with the alias. That is fixed, and the proof stops the next one. - If
ghcompatibility is ever worth having, the work is a GraphQL endpoint andGET /api/v1/meta, plus restoring the/api/v3prefix. That is a product decision about serving GraphQL, not a decision about a path segment, and it belongs in its own ADR.