API rename audit: /api/v3 to /api/v1
Audit for issue #211. The API lives at /api/v3 because it began by aiming at
parity with GitHub's v3 REST API, but the version in the path reads as this
API's own version, and this API is at its first version. This document records
every surface the rename touches, what breaks between the server rename and
the CLI release, and the recommended sequencing. Backward compatibility is not
required — there are no external users — but internal consumers that would
break mid-deploy are inventoried.
All counts are pinned to specific commits, because both repositories are under concurrent edit:
openagents.comat28aece5("Refuse a backdated issuer key retirement instead of unverifying history").openagentsmonorepo at022ebbd933.
Headline numbers
| Surface | Files | Hits |
|---|---|---|
| Phoenix repo, total tracked | 134 | 1,534 |
Phoenix lib/ |
30 | 366 |
Phoenix test/ |
52 | 768 |
Phoenix docs/ |
34 | 210 |
Phoenix priv/ (site docs and contract) |
14 | 150 |
Phoenix root (AGENTS.md 11, INVARIANTS.md 25, README.md 1) and .agents/ (3) |
4 | 40 |
| Monorepo, total tracked | 26 | 118 |
Monorepo packages/openagents-cli |
25 | ~115 |
Monorepo elsewhere (docs/teardowns/) |
1 | 3 |
Every hit is the literal string api/v3. There is no other spelling of the
version in code: the only non-path occurrences in lib/ are doc comments in
ApiRouteAuthority and the "api_version" => "v3" field in
lib/openagents_web/controllers/api_extension_controller.ex:383.
Phoenix repo inventory
Router
lib/openagents_web/router.ex mounts 23 scope blocks that begin with
/api/v3 (21 hits; two scopes carry longer prefixes such as
/api/v3/conversations/:conversation_id/boxes). The unversioned scopes
(/api, /api/operator, /api/contracts/..., /controller/...) are outside
the rename.
Route authority inventories
lib/openagents_web/api_route_authority.ex(185 hits) is the single authority inventory for every/api/v3route: each entry is a literal"METHOD /api/v3/..."key carrying principal, family, and error contract.test/openagents_web/api_route_authority_test.exsproves the inventory and the router agree, so the rename must change the router and this table in the same commit or CI fails.lib/openagents_web/route_authority.ex(58 hits) classifies every Phoenix route including the API surface, with the same CI-enforced agreement (test/openagents_web/route_authority_test.exs, 49 hits).
The GET /api/v3 root document and error envelope
lib/openagents_web/controllers/api_extension_controller.ex(22 hits) publishes the root document: endpoint strings per family, extension descriptors, and the"api_version" => "v3"field at line 383, which becomes"v1".lib/openagents_web/api_error.exbuildsdocumentation_urlfromEndpoint.url() <> "/api/v3"at line 194, so every refusal envelope links to the root document. One line, but it appears in every error response.
Links generated into responses
JSON views interpolate the path into url fields the API returns:
lib/openagents_web/controllers/issue_json.ex(issue and pull URLs)lib/openagents_web/controllers/stack_json.ex(stack, pull, and merge-async operation URLs)lib/openagents_web/controllers/pull_request_json.exlib/openagents_web/controllers/label_json.exlib/openagents_web/controllers/milestone_json.exlib/openagents_web/controllers/project_json.exlib/openagents_web/controllers/fleet_target_controller.ex(~p"/api/v3/admin/forge/targets/...")
Because these are interpolated at render time, they emit the new path the
moment the code deploys. Nothing stores an /api/v3 URL in the database.
Push receipts
lib/openagents/forge/git_http.ex:477 appends the WAL receipt to the
side-band output that git push prints:
openagents wal-receipt seq=N link=HASH (GET /api/v3/repos/OWNER/REPO/pushes/N)
The link value is a WAL chain hash, not a URL, so only the parenthesized
GET path changes — one line. Receipts already printed into old terminal
scrollback and transcripts will name a path that stops existing after the
alias is removed; that is acceptable, since the durable identity of a receipt
is seq plus link, not the hint path.
Contracts and manifests
priv/api-contracts/repositories-v1.json(15 hits) names routes as"METHOD /api/v3/..."strings. It is served atGET /api/contracts/repositories-v1.json— the serving route itself is unversioned and does not change.- The same file is vendored in the CLI at
packages/openagents-cli/contracts/repositories-v1.json, andpackages/openagents-cli/src/api-contract.tspins its SHA-256 (REPOSITORY_CONTRACT_SHA256), enforced bytest/api-contract.test.ts. Renaming the paths inside the contract changes the bytes, so both copies and the pinned hash must move together. - There is no OpenAPI document; the extension document at the API root and the contribution contract are the machine-readable manifests.
The agent front door
lib/openagents_web/contribution_contract.ex (28 hits) generates the
standing instructions served at openagents.com/agents.md and its JSON
twin, listing /api/v3 entry points. The rename flows through automatically
once this module changes; agents that cached the old page re-read it on
their next visit.
UI and LiveView
Two user-facing occurrences:
lib/openagents_web/live/home_live.ex:544— FAQ copy: "served under/api/v3. An existing client usually needs only a base URL change."lib/openagents_web/live/thread_index_live.ex:51— empty-state hint namingPOST /api/v3/threads.
Both are copy changes and need explicit sign-off under the repo's copy-change rule.
Data-rights export inventory
lib/openagents/data_rights/export_inventory.ex (13 hits) names the
/api/v3 route for each export mechanism, pinned by
test/openagents/data_rights/export_inventory_test.exs (11 hits).
Site docs, repo docs, and skills
priv/docs/(150 hits, 14 files) is the documentation served at/docs;rest-api.mdalone carries 88 hits,stacks-api.md20.docs/(210 hits, 34 files) — design docs, audits, runbooks. Historical audits can keep their/api/v3text with a dated note; live references (for exampledocs/api-authentication.md,docs/github-api-issues-projects-assessment.md) should be updated..agents/skills/openagents-work-management/SKILL.md(3 hits) instructs agents that relativeopenagents apipaths resolve under/api/v3/.AGENTS.md(11 hits,CLAUDE.mdis a symlink to it),INVARIANTS.md(25 hits),README.md(1 hit).
Tests
52 test files, 768 hits, all literal request paths
(~p"/api/v3/..." and string paths). Largest:
project_controller_test.exs (114), issue_controller_test.exs (74),
thread_controller_test.exs (73), forum_api_controller_test.exs (55),
route_authority_test.exs (49). All mechanical.
Monorepo inventory
All 26 files with hits sit in packages/openagents-cli except one teardown
doc (docs/teardowns/2026-08-23-openagents-coder-tui-agent-fleet-port-plan.md,
historical). No other package or app in the monorepo calls the /api/v3
surface.
The path is not one constant
src/endpoint.ts resolves only the origin (profile or --api-url); it
rejects any URL that carries a path. The version segment is scattered:
src/api-passthrough.ts:10—API_BASE_PATH = "/api/v3/", the resolver foropenagents apirelative paths.src/coder-thread.ts:69,src/coder-resume.ts:42,src/coder-transcript.ts:22— three separateTHREADS_PATH = "/api/v3/threads"constants for the coder lane, resume picker, and transcript writer.src/tracker-request.ts:103— the/api/v3/repos/{owner}/{repo}base thatissue-client.tsandproject-client.tsbuild on.src/repository-client.ts(12 literals),src/forum-client.ts(7),src/device-client.ts(2, the device-authorization pairing flow).src/coder-backends.ts,src/coder-skills.ts,src/cli.ts— comments and one skill-text line.
Shipped artifacts
The published npm package (@openagentsinc/cli, files: ["dist", "contracts", "README.md", "skills"]) ships:
contracts/repositories-v1.jsonwith the/api/v3route strings and the SHA-256 pin insrc/api-contract.ts(see the contract section above).skills/openagents-cli/SKILL.MDtelling agents that bareopenagents apipaths resolve under/api/v3/.
The published version on npm is 0.3.5, identical to the repo HEAD
version, so every installed CLI in existence hardcodes /api/v3.
Surfaces that do not change
- The git credential helper (
src/git-credential-helper.ts) matches on origin only, and the helper line written into git config is!openagents --api-url ORIGIN auth git-credential— no path. It keeps working across the rename as long as the installed binary is current; the helper itself makes no/api/v3request. - Machine pairing (
src/computer-client.ts) calls/controller/pairingsand/controller/status, which are outside the versioned scope. - The Phoenix contract route
GET /api/contracts/repositories-v1.jsonis unversioned; only the strings inside the body change.
The GitHub-compatibility question
Verdict: octokit depends only on response shape plus an explicit base URL;
gh depends on the literal /api/v3 path segment for any non-github.com
host.
Evidence:
- The installed
ghbinary (v2.89.0, built ongo-ghv2.13.0) contains the hardcoded format stringhttps://%s/api/v3/alongsidehttps://%s/api/graphql. This isgo-gh's REST prefix: whenGH_HOSTnames any host other than github.com,ghconstructs REST URLs ashttps://HOST/api/v3/...with no way to override the path segment. A forge that wantsGH_HOST=openagents.com gh ...to work must serve/api/v3at that literal path. - Octokit takes a full
baseUrl(for GitHub Enterprise Server the documented value ishttps://HOST/api/v3); the version segment is part of caller configuration, not the client. Only the response shape and headers matter. - The repo's own stated posture already stops short of drop-in tooling:
docs/github-api-issues-projects-assessment.md:390andpriv/docs/rest-api.md:346say pagination and link headers "do not provide complete Octokit orghparity", and the homepage FAQ promises only "a base URL change" — the octokit story, not theghstory. - One doc overstates it:
docs/episode-triage.md:1207says the shape means "ghand octokit work unchanged". After the rename that stays true for octokit and becomes false forghagainst a custom host, unless a permanent/api/v3alias is kept.
The rename therefore forfeits hypothetical GH_HOST-style gh drop-in
compatibility. Nothing in either repo currently exercises gh against
openagents.com, so this is a posture decision to record, not a breakage.
Mid-deploy breakage analysis
Ordered by severity, for a hard cutover with no alias:
- Every installed CLI breaks. Published
0.3.5equals repoHEAD; all issue, project, repo, forum,api, coder, resume, and device-pairing commands 404 until the user updates to a release that targets/api/v1. The server cannot be renamed before that release exists, and the release cannot ship before the server serves/api/v1— a deadlock unless one deploy serves both. - In-flight coder sessions die. A running
openagents codersession holds a thread grant and appends events toPOST /api/v3/threads/{id}/events; long sessions span deploys. A hard cutover mid-session breaks transcript writes and the exit-timeDELETE /api/v3/threads/{id}revocation. - Agent instructions go stale. The served
agents.md, the skill inside the npm package, and.agents/skills/openagents-work-managementall name/api/v3. Agents following cached instructions 404 until they re-read. - Push receipt hint paths. New pushes print the new path immediately
(rendered, not stored). Old receipts in scrollback and transcripts point
at a dead path once v3 stops answering; the WAL
seq/linkidentity is unaffected. - Not affected: the git credential helper (origin-only), machine
pairing (
/controller/*), the contract-serving route (/api/contracts/...), and the database (no stored/api/v3URLs).
Recommended sequencing
Serve both paths for one deploy, as a pure alias, then delete it. The alias costs roughly 20 lines and one deploy of patience; a hard cutover saves those lines but breaks every installed CLI and every in-flight coder thread during the window between the server deploy and the npm publish. Given the deadlock in breakage item 1, the alias is the cheapest ordering that never leaves a consumer without a working path.
- Phoenix, one commit: rename to
/api/v1and alias/api/v3. Mechanical rename across router,ApiRouteAuthority,RouteAuthority, controllers, JSON views,ApiError,ContributionContract, the extension document (api_version: "v1"), the push-receipt line, the export inventory,priv/api-contracts/repositories-v1.json,priv/docs, tests,AGENTS.md,INVARIANTS.md, and.agents/skills. Add one plug ahead of the router that rewritespath_info["api", "v3" | rest]to["api", "v1" | rest]— a transparent rewrite, not a redirect, because old clients POST and do not follow redirects reliably. The router stays single-sourced at/api/v1, so the authority tests keep proving the real surface; one test pins the alias. All generated links and receipts emit/api/v1from this deploy on. - Monorepo: point the CLI at
/api/v1, publish0.4.0. Update the scattered literals (or, better, route them through one exported constant), regeneratecontracts/repositories-v1.jsonandREPOSITORY_CONTRACT_SHA256, update the shipped skill and README, bump, publish. Old0.3.5installs keep working through the alias; new installs use v1 natively. - Update instructions and docs in both repos (served docs are part of
step 1's commit; monorepo docs ride step 2). Decide the
ghposture explicitly: correctdocs/episode-triage.md's "gh works unchanged" claim, or commit to a permanent/api/v3alias forGH_HOSTdrop-in. - Delete the alias once the fleet's installed CLIs are
>= 0.4.0— remove the plug and its test. If step 3 chose to keepghdrop-in, skip this step and record the alias as permanent inINVARIANTS.md.
Estimated diff size
- Phoenix: about 1,530 single-line textual changes across ~134 files —
a mechanical
sedplusmix precommit, with three files needing thought: the router (23 scopes plus the alias plug),api_extension_controller.ex(api_versionfield), and the two LiveView copy lines (copy sign-off). Net new code: the rewrite plug (~20 lines) and one alias test. - Monorepo: about 115 single-line changes across 25 files in
packages/openagents-cli, plus the contract SHA recompute, a version bump, and an npm publish. Optional consolidation of the path constants adds a small refactor.
Follow-up issues
- Rename the API surface to
/api/v1and alias/api/v3— Phoenix serves the whole surface at/api/v1, emits v1 links and receipts, and transparently rewrites/api/v3requests, all in one deploy. - Ship CLI
0.4.0targeting/api/v1— every hardcoded path, the vendored contract and its SHA pin, and the shipped skill name v1; published to npm. - Consolidate the CLI's API base path into one constant — the coder thread, resume, transcript, tracker, repository, forum, and device clients all import a single versioned base, so the next rename is one line.
- Decide and record the
ghcompatibility posture — either correct the "gh works unchanged" claim indocs/episode-triage.mdand the compat framing, or make the/api/v3alias permanent forGH_HOSTdrop-in; record the choice inINVARIANTS.md. - Delete the
/api/v3alias — after confirming installed CLIs are>= 0.4.0, remove the rewrite plug and its test (skipped if issue 4 keeps the alias). - Sweep remaining
/api/v3references in docs and skills — update live docs in both repos and annotate historical audits, so no standing instruction names the dead path.