Repository navigation
feat(apidocs)!: Declare the API base path in the spec's server URL - #131065
Conversation
The OpenAPI server URL was the bare origin, and every path spelled out /api/0, so
clients had to know the prefix, and the version in it, instead of reading it from
the spec. The server URL is now https://{region}.sentry.io/api/0 and paths are
relative to it.
A last postprocessing hook strips the base path, so the hooks before it still see
full paths. It also adds the base path to per-endpoint servers, which name origins.
BREAKING CHANGE: Spec paths no longer start with /api/0; join them to the server
URL. getsentry/sentry-api-schema#148 handles both shapes.
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…148) **Merge order:** [#148](#148) → [getsentry/sentry-docs#20787](getsentry/sentry-docs#20787) → [getsentry/sentry#131065](getsentry/sentry#131065) → the getsentry/toolkit bump, after `@sentry/api` 2.0.0 is published. Docs has to merge before sentry#131065. When that merges, the spec sync commits the new spec to sentry-api-schema, which auto-releases `@sentry/api` and auto-bumps the schema in sentry-docs. Without sentry-docs#20787 merged first, the docs would drop `/api/0` from paths and curl examples. This PR supersedes #147, which hard-coded `API_PREFIX`. Per @BYK, the server decides the base path. `/api/0` comes from the spec's server URL. It's exported as `API_BASE_PATH`, and every path in the package is relative to it. Nothing here hard-codes `0`. A future `/api/1` spec would produce `API_BASE_PATH = "/api/1"` with no code change. **Design** - `lib/api-base-path.mjs` reads the base path from the path of `servers[0].url`, makes `paths` relative, and trims server URLs to their origin. That includes the per-operation override on `listSeerModels`. - **Until the sentry PR lands,** the base path is the `/api/<version>` prefix that every path shares. It's marked TODO. Both spec shapes build the same `src/`; I checked this with `diff -r`. So the sentry change only syncs a byte-identical package through the bot. - **`baseUrl` stays the Sentry origin.** That's what users have, including self-hosted URLs and `links.regionUrl`. The patched `buildUrl` builds the request URL as origin + `API_BASE_PATH` + operation path. - **Exported** from `@sentry/api`, `@sentry/api/client` and `@sentry/api/operations`. - **The four `/api/0` copies are gone:** in `core.ts`, `buildSentryApiUrl`, `parseApiPathScope` and cache invalidation. So are the copies in the idempotent matcher, `normalize-spec` and `source-analysis`. Matching still finds the base path anywhere in the URL, as before. **Before / After** ```ts // @sentry/api/operations { functionName: "listOrganizations", path: "/api/0/organizations/" } { functionName: "listOrganizations", path: "/organizations/" } import { API_BASE_PATH } from "@sentry/api"; // "/api/0", from servers[0].url // raw requests: url is relative to API_BASE_PATH sentry.client.prepareRequest({ method: "GET", url: "/api/0/organizations/" }); // before sentry.client.prepareRequest({ method: "GET", url: "/organizations/" }); // after // both send https://sentry.io/api/0/organizations/ ``` **Breaking: major bump to 2.0.0** - `operations[].path` and `IdempotentOperation.path` are relative. Build-time lists derived from the catalog must be regenerated, such as toolkit's `idempotent-operations.json`. - Raw `client.request`, `client.get` and `prepareRequest` take relative `url`s. An old `/api/0/...` URL now gets a doubled base path. - Generated function names, types and the request URLs of typed operations don't change: `sentry-client.gen.ts` is byte-identical. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A640c5343d03e547097aa9b8a711daaf5 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
**Depends on:** `@sentry/api` 2.0.0, which is published ([getsentry/sentry-api-schema#148](getsentry/sentry-api-schema#148)). This is the last step of the chain sentry-api-schema#148 → [sentry-docs#20787](getsentry/sentry-docs#20787) → [sentry#131065](getsentry/sentry#131065) → this PR. Sentry's OpenAPI spec is moving `/api/0` out of every path and into its server URL. `@sentry/api` 2.0 reads the base path from that URL, exports it as `API_BASE_PATH`, and makes operation paths relative. This PR drops toolkit's private `/api/0` copies and uses the export instead, so the version number lives only in the spec. Before: ```ts const API_PREFIX = "/api/0"; // cli/src/commands/api.ts this.apiPrefix = `${protocol}://${host}/api/0`; // mcp-core api-client path: operation.path, // "/api/0/organizations/" in 1.x ``` After: ```ts import { API_BASE_PATH } from "@sentry/api/client"; this.apiPrefix = `${protocol}://${host}${API_BASE_PATH}`; path: `${API_BASE_PATH}${operation.path}`, // still "/api/0/organizations/" ``` - **CLI endpoint index:** it keeps full request paths, so `sentry schema` and `sentry api` still show and accept `/api/0/...`. - **Requests:** CLI raw requests now pass relative URLs to `prepareRequest`. - **Unchanged on purpose:** user-facing help text and examples still say `/api/0`. - **Breaking:** only the `@sentry/api` 1.x → 2.0.0 bump. CLI and MCP behavior doesn't change, so no toolkit major is needed. **#2229 (stacked on #1807):** on 2.0 it needs two changes: - replace its local `API_PREFIX = "/api/0"` in `error-reporting.ts` with `API_BASE_PATH`; - build `api-path-templates.json` from `` `${API_BASE_PATH}${op.path}` ``, because `op.path` is now relative. **Checks:** - `pnpm run tsc` and `pnpm run lint` pass. - mcp-core vitest: 2301 passed. - The CLI api, schema, sentry-client, error-reporting, issue-links and script tests: 82 files, 1685 passed. - `pnpm install --frozen-lockfile` passes with the `minimumReleaseAgeExclude` entry moved from 1.83.0 to 2.0.0. <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A640c5343d03e547097aa9b8a711daaf5 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
> **Merge order:** [getsentry/sentry-api-schema#148](getsentry/sentry-api-schema#148) → [#20787](#20787) → [getsentry/sentry#131065](getsentry/sentry#131065) → the getsentry/toolkit bump, after `@sentry/api` 2.0.0 is published. Docs has to merge before sentry#131065. When that merges, the spec sync commits the new spec to sentry-api-schema, which auto-releases `@sentry/api` and auto-bumps the schema in sentry-docs. Without this PR merged first, the docs would drop `/api/0` from paths and curl examples. This PR also works with the current spec. ## DESCRIBE YOUR PR Sentry's OpenAPI spec is moving `/api/0` out of every path and into its server URL, so clients read the base path (and the API version in it) from the spec ([getsentry/sentry-api-schema#148](getsentry/sentry-api-schema#148), then the getsentry/sentry change). The API reference ignored the spec-level `servers`, so after that change, pages and curl examples would drop `/api/0`. `apiLocation()` joins the server URL's path to each operation path. The server it shows is only the origin, still `https://sentry.io` by default, or the operation's own server. Before, with the new spec shape: ``` GET /organizations/ curl https://sentry.io/organizations/ ``` After, with either spec shape: ``` GET /api/0/organizations/ curl https://sentry.io/api/0/organizations/ ``` **No visual change, so no screenshots.** I ran `apiCategories()` against a spec built from getsentry/sentry master and against the same spec built with the change. The page data was byte-identical, including the per-operation `https://{region}.sentry.io` server on `/api/0/seer/models/`. Checks: the `resolveOpenAPI.spec.ts` vitest suite passes, with one new table test covering both spec shapes and an operation-level server. ESLint and Prettier pass on the changed files. ## IS YOUR CHANGE URGENT? - [ ] Urgent deadline (GA date, etc.): YYYY-MM-DD - [ ] Other deadline: YYYY-MM-DD - [x] No deadline: Not urgent, can wait up to 1 week+ It does block the getsentry/sentry spec change from merging. ## PRE-MERGE CHECKLIST - [ ] Checked Vercel preview for correctness, including links - [ ] PR was reviewed and approved by any necessary SMEs (subject matter experts) - [ ] PR was reviewed and approved by a member of the [Sentry docs team](https://github.com/orgs/getsentry/teams/docs) <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A640c5343d03e547097aa9b8a711daaf5 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
BYK
left a comment
There was a problem hiding this comment.
Wondering if we can somehow sync the spectacular settings with the api's mounted django route to prevent any future issues?
Mount sentry.api.urls at API_BASE_PATH and build the spec's server URL from it, so the two can't drift apart.
Yes, done in 37d5255. There's now one constant,
The constant lives in Checks:
getsentry mounts its own |
path() changed the mount's route string from "^api/0/" to "api/0/", which broke dev toolbar analytics routes and the generated control silo URLs. Build the same regex from API_BASE_PATH instead.
Main has been red since the spec sync from getsentry/sentry#131065 landed (36d0d11). That sync made spec paths relative to the server URL's `/api/0`. Two tests still look up or compare full `/api/0` paths, so Build fails and no `@sentry/api` release goes out until this lands. - `test/descriptions.test.ts` (from #142): `spec.paths["/api/0/organizations/{organization_id_or_slug}/dashboards/"]` → `spec.paths["/organizations/{organization_id_or_slug}/dashboards/"]` - `test/docs.test.ts` (from #150, written against the old spec), comparing the export's paths with the spec's: `` `${operation.method} ${API_BASE_PATH}${operation.path}` `` → `` `${operation.method} ${operation.path}` `` No other test reads `spec.paths` by a full `/api/0` key. The remaining `/api/0` strings in tests are request URLs, which are still correct. Checks: `pnpm build`, `pnpm typecheck`, `pnpm test` (681 passed) and `check-script-modes.sh` pass. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A640c5343d03e547097aa9b8a711daaf5 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A640c5343d03e547097aa9b8a711daaf5/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
The public OpenAPI spec's server URL was the bare origin, and every path spelled out
/api/0. Clients had to know the prefix, and the API version in it, instead of reading it from the spec. Now the server URL carries the versioned base path and paths are relative to it.Before:
After:
How:
SPECTACULAR_SETTINGS["SERVERS"]sets the URL tohttps://{region}.sentry.io/api/0.relative_paths_hook, strips that base path from every path, so the hooks before it still see full paths. A path outside the base path fails the build.servers(only/seer/models/today:https://{region}.sentry.io/api/0). Endpoint classes keep naming origins.SCHEMA_PATH_PREFIX_TRIM: it doesn't trim theAPPEND_PATHSwe use, andcustom_postprocessing_hookmatches on full paths.Versioning: the spec names
/api/0once. A future v1 is a new server entry or spec with/api/1, and downstream clients read the path fromservers[0].url. I picked this over an/api/{version}server variable because a v1 overhaul would come with different paths and schemas, which one spec can't describe. Self-hosted stays as before: clients swap the origin and keep the path.Breaking change for anyone reading the spec: paths no longer start with
/api/0, so join them to the server URL. Known consumers:@sentry/api: handled by sentry-api-schemaAdded support for top level exception handling #148, which ships as 2.0.0. It reads both shapes and generates byte-identical clients from this spec and the master spec.@sentry/apiand follows after the 2.0.0 release.Nothing else in this repo reads the spec, apart from the
openapi.ymlsync and theopenapi-diffcomment workflow. The private investigation schema reuses these settings and now has the same shape; nothing consumes it.Checks (run locally):
make build-api-docsandmake build-investigation-api-docsboth pass. Against master, the derefed spec differs only inservers, the path keys and the seer op server.pnpm run validate-api-examplespasses.pytest tests/apidocspasses (167). Tests changed:RelativePathsHookTestis new.APIDocsTestCasekeeps the server URL path when it swaps the origin. Without that, openapi-core can't match any request (ServerNotFound).via @BYK.
--
View Session [Sentry]