From 087017edf4cfa5b4f93aebe4fdbf7b012f086794 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Wed, 9 Sep 2026 12:17:12 -0400 Subject: [PATCH 1/2] feat(api): Badge experimental endpoints on API reference pages Sentry marks endpoints published under the PUBLIC_EXPERIMENTAL status with x-sentry-experimental on the operation, but nothing read it, so those endpoints rendered as though they were stable. Model the extension, carry it onto the API type next to the existing deprecated flag, and render a badge above the request block. --- src/build/open-api/types.ts | 1 + src/build/resolveOpenAPI.ts | 2 ++ src/components/apiPage/index.tsx | 10 ++++++++++ 3 files changed, 13 insertions(+) diff --git a/src/build/open-api/types.ts b/src/build/open-api/types.ts index 3ed36b1a07cb13..d9311e252f7015 100644 --- a/src/build/open-api/types.ts +++ b/src/build/open-api/types.ts @@ -72,6 +72,7 @@ export type DeRefedOpenAPI = { description?: string; security?: any; servers?: ServerMeta[]; + 'x-sentry-experimental'?: boolean; }; }; }; diff --git a/src/build/resolveOpenAPI.ts b/src/build/resolveOpenAPI.ts index 4b4cf9b1a9b30a..589c68935f8716 100644 --- a/src/build/resolveOpenAPI.ts +++ b/src/build/resolveOpenAPI.ts @@ -64,6 +64,7 @@ export type API = { apiPath: string; bodyParameters: APIParameter[]; deprecated: boolean; + experimental: boolean; method: string; name: string; pathParameters: APIParameter[]; @@ -148,6 +149,7 @@ async function apiCategoriesUncached(): Promise { method, name: cleanName, deprecated: isDeprecated, + experimental: apiData['x-sentry-experimental'] === true, server, slug: slugify(cleanName), summary: apiData.summary diff --git a/src/components/apiPage/index.tsx b/src/components/apiPage/index.tsx index c0caa97bbcfd3f..3599b95e160f39 100644 --- a/src/components/apiPage/index.tsx +++ b/src/components/apiPage/index.tsx @@ -125,6 +125,16 @@ export function ApiPage({api}: Props) { }; return ( + {api.experimental && ( +
+ + Experimental + + + This API is under active development and may change. + +
+ )}
From 67cb846815378cbdf46342a18a2a249e02940b68 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Thu, 10 Sep 2026 13:55:59 -0400 Subject: [PATCH 2/2] fix(api): Strip the fallback experimental notice when badging Sentry prepends a text notice to every experimental operation's description so consumers without a badge still warn the reader. Rendering both said the same sentence twice on the page. Strip the notice when we render the badge, which also makes the two changes safe to merge in either order. --- src/build/resolveOpenAPI.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/build/resolveOpenAPI.ts b/src/build/resolveOpenAPI.ts index 589c68935f8716..6f8d6a27cf6dc3 100644 --- a/src/build/resolveOpenAPI.ts +++ b/src/build/resolveOpenAPI.ts @@ -98,6 +98,11 @@ function slugify(s: string): string { const DEPRECATED_PREFIX_REGEX = /^\(DEPRECATED\)\s*/; +// Sentry prepends this notice to the description of every experimental operation, so +// that consumers with no badge of their own still warn the reader. We render a badge, +// so strip it rather than saying the same thing twice. +const EXPERIMENTAL_NOTICE_REGEX = /^\*\*Experimental:\*\*[^\n]*\n+/; + function isDeprecatedOperationId(operationId: string | undefined): boolean { return operationId ? DEPRECATED_PREFIX_REGEX.test(operationId) : false; } @@ -136,6 +141,7 @@ async function apiCategoriesUncached(): Promise { const isDeprecated = isDeprecatedOperationId(apiData.operationId) || isDeprecatedOperationId(apiData.summary); + const isExperimental = apiData['x-sentry-experimental'] === true; const titleSource = apiData.summary || apiData.operationId || ''; const cleanName = stripDeprecatedPrefix(titleSource); @@ -149,13 +155,15 @@ async function apiCategoriesUncached(): Promise { method, name: cleanName, deprecated: isDeprecated, - experimental: apiData['x-sentry-experimental'] === true, + experimental: isExperimental, server, slug: slugify(cleanName), summary: apiData.summary ? stripDeprecatedPrefix(apiData.summary) : apiData.summary, - descriptionMarkdown: apiData.description, + descriptionMarkdown: isExperimental + ? apiData.description?.replace(EXPERIMENTAL_NOTICE_REGEX, '') + : apiData.description, pathParameters: (apiData.parameters || []).filter( p => p.in === 'path' ) as APIParameter[],