Repository navigation
feat(api-client)!: Read the API base path from the spec's server URL - #148
Merged
Merged
Conversation
The client hard-coded /api/0 in four places, and the catalog, SDK URLs
and internal keys all spelled it out in every path. Read it from the
OpenAPI spec's server URL instead, export it as API_BASE_PATH, and keep
every path relative to it.
The build accepts both spec shapes: the current one, where every path
starts with /api/0, and the one where servers carries /api/0 and paths
are relative. Both produce the same package, so the getsentry/sentry
spec change that follows is a no-op here.
BREAKING CHANGE: Catalog paths (`operations[].path`), `IdempotentOperation`
paths, and raw `client.request`/`prepareRequest` URLs are relative to
`API_BASE_PATH`, such as `/organizations/{organization_id_or_slug}/`.
`baseUrl` stays the Sentry origin; the client adds the base path.
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
sentry-junior
Bot
requested review from
BYK,
MathurAditya724 and
betegon
as code owners
November 13, 2026 12:16
This was referenced Nov 13, 2026
BYK
requested changes
Nov 13, 2026
Split the spec's server URL with the URL parser, and find the legacy /api/<version> prefix by path segments. A new test fails on any regex that matches a URL scheme separator, and AGENTS.md documents the rule.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1f41687. Configure here.
BYK
approved these changes
Nov 13, 2026
2 tasks done
This was referenced Nov 13, 2026
BYK
added a commit
to getsentry/toolkit
that referenced
this pull request
Nov 13, 2026
**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>
BYK
added a commit
to getsentry/sentry-docs
that referenced
this pull request
Nov 13, 2026
> **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
added a commit
to getsentry/sentry
that referenced
this pull request
Nov 13, 2026
…131065) > **Depends on:** [getsentry/sentry-api-schema#148](getsentry/sentry-api-schema#148) and [getsentry/sentry-docs#20787](getsentry/sentry-docs#20787). **Merge both first.** `openapi.yml` syncs this spec to sentry-api-schema on push to master. That repo then auto-releases `@sentry/api` and cascades into sentry-docs, and both still assume `/api/0` in every path until those PRs land. 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: ```json "servers": [{ "url": "https://{region}.sentry.io", "variables": { "region": { ... } } }], "paths": { "/api/0/organizations/{organization_id_or_slug}/releases/": { ... } } ``` After: ```json "servers": [{ "url": "https://{region}.sentry.io/api/0", "variables": { "region": { ... } } }], "paths": { "/organizations/{organization_id_or_slug}/releases/": { ... } } ``` **How:** - `SPECTACULAR_SETTINGS["SERVERS"]` sets the URL to `https://{region}.sentry.io/api/0`. - A new last postprocessing hook, `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. - The hook also appends the base path to per-endpoint `servers` (only `/seer/models/` today: `https://{region}.sentry.io/api/0`). Endpoint classes keep naming origins. - I didn't use drf-spectacular's `SCHEMA_PATH_PREFIX_TRIM`: it doesn't trim the `APPEND_PATHS` we use, and `custom_postprocessing_hook` matches on full paths. **Versioning:** the spec names `/api/0` once. A future v1 is a new server entry or spec with `/api/1`, and downstream clients read the path from `servers[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-schema#148, which ships as 2.0.0. It reads both shapes and generates byte-identical clients from this spec and the master spec. - docs.sentry.io API reference: handled by sentry-docs#20787. It renders the same page data from both shapes. - getsentry/toolkit consumes `@sentry/api` and follows after the 2.0.0 release. Nothing else in this repo reads the spec, apart from the `openapi.yml` sync and the `openapi-diff` comment workflow. The private investigation schema reuses these settings and now has the same shape; nothing consumes it. **Checks** (run locally): - `make build-api-docs` and `make build-investigation-api-docs` both pass. Against master, the derefed spec differs only in `servers`, the path keys and the seer op server. - `pnpm run validate-api-examples` passes. - `pytest tests/apidocs` passes (167). Tests changed: - `RelativePathsHookTest` is new. - `APIDocsTestCase` keeps the server URL path when it swaps the origin. Without that, openapi-core can't match any request (`ServerNotFound`). - Two endpoint tests read schema entries by path and now use relative keys. - prek and mypy pass on the changed files. <!-- 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
added a commit
that referenced
this pull request
Nov 13, 2026
Stacked on #149. Ported from getsentry/sentry#130538, per @BYK's request there: get the docs aligned here first, then move `@sentry/api` into getsentry/sentry. This adds the data sentry-docs needs to build the API reference from the `@sentry/api` version it pins, so it no longer has to read `openapi-derefed.json` from a pinned commit of this repository. The sentry-docs adapter is getsentry/sentry-docs#20679. ## Changes from the sentry version 2.0.0 (#148) made every path relative to the API base path and trimmed server URLs to their origin. The export now follows the same contract: - operation `path`s are relative, such as `/organizations/`, the same as `@sentry/api/operations`; - the base path ships as `API_BASE_PATH` (`/api/0`) and as `apiBasePath` in `extractDocs()` output, so a page builds the full path as `${API_BASE_PATH}${operation.path}`; - per-operation `servers` are origins. `extractDocs()` applies `splitApiBasePath` to a copy of the spec it gets, so the published data and a preview of a local spec have the same shape. It accepts both the current spec and one that declares the base path in `servers`. **sentry-docs#20679 needs a follow-up for this:** its adapter has to prepend `API_BASE_PATH` to paths, like sentry-docs#20787 does for 2.0.0. I'll push that once this shape is settled. ## What it adds - **`scripts/generate-docs.mjs`**, next to the `operations` generator. It runs `lib/docs-catalog.mjs` `extractDocs(spec)` on the original `openapi-derefed.json` and emits `src/docs.ts`. That file holds the data as a JSON string literal, so tsc doesn't infer a type for 3.7 MB of data. It ships as **`@sentry/api/docs`** (`dist/docs.js` plus `.d.ts`). - **Shape** (`lib/docs-types.ts`): - `apiBasePath` / `API_BASE_PATH`. - `tags`: name, description, `sidebarName` (`x-sidebar-name`) and `displayDescription` (`x-display-description`). - `operations`, in spec order: `operationId`, method and relative path, with summary and description copied as written. Also tags, `deprecated`, `experimental` (`x-sentry-experimental`), server origins, security requirements, and parameters (path-level merged with operation overrides) with `in`, `required`, description, `experimental` and schema. The request body comes from its first content type (content type, `required`, schema, example), and every response has its status, description, content type, schema and `example`/`examples`. - **`extractDocs` is exported too**, so sentry-docs' `OPENAPI_LOCAL_PATH` preview of an unreleased local spec goes through the same projection as the published data. - `lib/http-methods.mjs` holds the HTTP method set that both catalogs use. ## The export's shape is now a semver contract sentry-docs, and anything else that imports `@sentry/api/docs`, depends on these field names and types. Removing or renaming a field, or changing its type, is a **major** release. Adding fields is a minor release. The README says so. ## Size Measured with `npm pack --dry-run` from clean builds of #149 and this branch, using the spec in this repository: | | tgz | unpacked | files | |---|---|---|---| | #149 | 3,385,825 B | 35,812,862 B | 80 | | this PR | 3,828,867 B | 39,660,930 B | 86 | | change | **+443 KB (+13%)** | **+3.85 MB (+11%)** | +6 | Almost all of it is `dist/docs.js` (3,840,561 B). It's a separate entry point, so `@sentry/api` and `@sentry/api/client` users like toolkit don't load it unless they import `./docs`. They only pay for the download. ## Tests - `test/docs.test.ts`: - A fixture test pins the exact output shape. Its spec declares `/api/0` in `servers` and an operation server override under it, so the test also covers the relative paths, `apiBasePath` and origin-only servers. It also covers path-level parameter overrides, experimental flags, multiple content types, responses without content, named examples and security. - Drift tests compare the published `@sentry/api/docs` with `openapi-derefed.json`: - the export equals `extractDocs(spec)`, and `API_BASE_PATH` is `/api/0`; - tag and operation order matches the spec, with `API_BASE_PATH` plus each relative path equal to the spec's path; - for every operation, the text, parameters, request schema and example, and response schemas and examples match the spec directly. - `test/package.test.ts`: the packed-tarball smoke test imports `@sentry/api/docs`. `pnpm build`, `pnpm typecheck` and `pnpm test` pass locally (54 files, 679 tests). No UI change here. The sentry-docs PR carries the API reference screenshots. ## Merge order #149, this PR, the normal release of an `@sentry/api` version with `./docs`, then getsentry/sentry-docs#20679 with its `API_BASE_PATH` follow-up. That PR's dependency bump is its last step. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A69da19b385017e68b7f9eace56e9477f --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A69da19b385017e68b7f9eace56e9477f) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A69da19b385017e68b7f9eace56e9477f/?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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Merge order: getsentry/sentry-api-schema#148 → getsentry/sentry-docs#20787 → getsentry/sentry#131065 → the getsentry/toolkit bump, after
@sentry/api2.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/apiand auto-bumps the schema in sentry-docs. Without sentry-docs#20787 merged first, the docs would drop/api/0from paths and curl examples. This PR supersedes #147, which hard-codedAPI_PREFIX.Per @BYK, the server decides the base path.
/api/0comes from the spec's server URL. It's exported asAPI_BASE_PATH, and every path in the package is relative to it. Nothing here hard-codes0. A future/api/1spec would produceAPI_BASE_PATH = "/api/1"with no code change.Design
lib/api-base-path.mjsreads the base path from the path ofservers[0].url, makespathsrelative, and trims server URLs to their origin. That includes the per-operation override onlistSeerModels./api/<version>prefix that every path shares. It's marked TODO. Both spec shapes build the samesrc/; I checked this withdiff -r. So the sentry change only syncs a byte-identical package through the bot.baseUrlstays the Sentry origin. That's what users have, including self-hosted URLs andlinks.regionUrl. The patchedbuildUrlbuilds the request URL as origin +API_BASE_PATH+ operation path.@sentry/api,@sentry/api/clientand@sentry/api/operations./api/0copies are gone: incore.ts,buildSentryApiUrl,parseApiPathScopeand cache invalidation. So are the copies in the idempotent matcher,normalize-specandsource-analysis. Matching still finds the base path anywhere in the URL, as before.Before / After
Breaking: major bump to 2.0.0
operations[].pathandIdempotentOperation.pathare relative. Build-time lists derived from the catalog must be regenerated, such as toolkit'sidempotent-operations.json.client.request,client.getandprepareRequesttake relativeurls. An old/api/0/...URL now gets a doubled base path.sentry-client.gen.tsis byte-identical.Co-Authored-By: BYK 126780+BYK@users.noreply.github.com
via @BYK.
--
View Session [Sentry]