From c32902860b8e89619f3ca50367b8cc8f83359566 Mon Sep 17 00:00:00 2001 From: Konrad Staniszewski Date: Tue, 24 Mar 2026 14:01:16 -0700 Subject: [PATCH] feat(api): document public API authentication --- .../sourcebot-public.openapi.json | 36 ++++++++++++++-- docs/docs/api-reference/overview.mdx | 7 ++++ packages/web/src/openapi/publicApiDocument.ts | 41 ++++++++++++++++--- packages/web/src/openapi/publicApiSchemas.ts | 1 - 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/docs/api-reference/sourcebot-public.openapi.json b/docs/api-reference/sourcebot-public.openapi.json index 8c9cfeec6..d58d62d94 100644 --- a/docs/api-reference/sourcebot-public.openapi.json +++ b/docs/api-reference/sourcebot-public.openapi.json @@ -3,8 +3,17 @@ "info": { "title": "Sourcebot Public API", "version": "v4.15.6", - "description": "OpenAPI description for the public Sourcebot REST endpoints used for search, repository listing, and file browsing." + "description": "OpenAPI description for the public Sourcebot REST endpoints used for search, repository listing, and file browsing. Authentication is instance-dependent: API keys are the standard integration mechanism, OAuth bearer tokens are EE-only, and some instances may allow anonymous access." }, + "security": [ + { + "bearerAuth": [] + }, + { + "sourcebotApiKey": [] + }, + {} + ], "tags": [ { "name": "Search", @@ -512,8 +521,7 @@ "properties": { "version": { "type": "string", - "description": "Running Sourcebot version.", - "example": "v4.15.2" + "description": "Running Sourcebot version." } }, "required": [ @@ -657,11 +665,25 @@ "additionalProperties": false } }, - "parameters": {} + "parameters": {}, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Send either a Sourcebot API key (`sbk_...` or legacy `sourcebot-...`) or, on EE instances with OAuth enabled, an OAuth access token (`sboa_...`) in the Authorization header." + }, + "sourcebotApiKey": { + "type": "apiKey", + "in": "header", + "name": "X-Sourcebot-Api-Key", + "description": "Send a Sourcebot API key (`sbk_...` or legacy `sourcebot-...`) in the X-Sourcebot-Api-Key header." + } + } }, "paths": { "/api/search": { "post": { + "operationId": "search", "tags": [ "Search" ], @@ -712,6 +734,7 @@ }, "/api/stream_search": { "post": { + "operationId": "streamSearch", "tags": [ "Search" ], @@ -763,6 +786,7 @@ }, "/api/repos": { "get": { + "operationId": "listRepositories", "tags": [ "Repositories" ], @@ -878,6 +902,7 @@ }, "/api/version": { "get": { + "operationId": "getVersion", "tags": [ "Misc" ], @@ -898,6 +923,7 @@ }, "/api/source": { "get": { + "operationId": "getFileSource", "tags": [ "Files" ], @@ -974,6 +1000,7 @@ }, "/api/tree": { "post": { + "operationId": "getFileTree", "tags": [ "Files" ], @@ -1043,6 +1070,7 @@ }, "/api/files": { "post": { + "operationId": "listFiles", "tags": [ "Files" ], diff --git a/docs/docs/api-reference/overview.mdx b/docs/docs/api-reference/overview.mdx index 994ea3174..b2c180758 100644 --- a/docs/docs/api-reference/overview.mdx +++ b/docs/docs/api-reference/overview.mdx @@ -7,6 +7,13 @@ You can fetch the OpenAPI document for your Sourcebot instance from `/api/openap This API reference is generated from the web app's Zod schemas and OpenAPI registry. Mintlify renders it from the checked-in spec at [`/api-reference/sourcebot-public.openapi.json`](/api-reference/sourcebot-public.openapi.json). +For authenticated access, create an API key in Sourcebot from **Settings -> API Keys** and send it with either: + +- `X-Sourcebot-Api-Key: sbk_...` +- `Authorization: Bearer sbk_...` + +Some instances may also allow anonymous access for these endpoints. On EE instances with OAuth enabled, bearer tokens with the `sboa_...` prefix are also accepted. + The first documented endpoints include: - `/api/search` diff --git a/packages/web/src/openapi/publicApiDocument.ts b/packages/web/src/openapi/publicApiDocument.ts index c3a1f71db..56d7aba52 100644 --- a/packages/web/src/openapi/publicApiDocument.ts +++ b/packages/web/src/openapi/publicApiDocument.ts @@ -1,6 +1,6 @@ import { OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; import type { ZodTypeAny } from 'zod'; -import type { SchemaObject } from 'openapi3-ts/oas30'; +import type { ComponentsObject, SchemaObject, SecuritySchemeObject } from 'openapi3-ts/oas30'; import { publicFileSourceRequestSchema, publicFileSourceResponseSchema, @@ -45,6 +45,20 @@ const publicGetTreeResponseSchema: SchemaObject = { additionalProperties: false, }; +const securitySchemes: Record = { + bearerAuth: { + type: 'http', + scheme: 'bearer', + description: 'Send either a Sourcebot API key (`sbk_...` or legacy `sourcebot-...`) or, on EE instances with OAuth enabled, an OAuth access token (`sboa_...`) in the Authorization header.', + }, + sourcebotApiKey: { + type: 'apiKey', + in: 'header', + name: 'X-Sourcebot-Api-Key', + description: 'Send a Sourcebot API key (`sbk_...` or legacy `sourcebot-...`) in the X-Sourcebot-Api-Key header.', + }, +}; + function jsonContent(schema: ZodTypeAny | SchemaObject) { return { 'application/json': { @@ -66,6 +80,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'post', path: '/api/search', + operationId: 'search', tags: [searchTag.name], summary: 'Run a blocking code search', request: { @@ -87,6 +102,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'post', path: '/api/stream_search', + operationId: 'streamSearch', tags: [searchTag.name], summary: 'Run a streaming code search', description: 'Returns a server-sent event stream. Each event data payload is a JSON object describing either a chunk, final summary, or error.', @@ -113,6 +129,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'get', path: '/api/repos', + operationId: 'listRepositories', tags: [reposTag.name], summary: 'List repositories', request: { @@ -147,6 +164,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'get', path: '/api/version', + operationId: 'getVersion', tags: [miscTag.name], summary: 'Get Sourcebot version', responses: { @@ -160,6 +178,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'get', path: '/api/source', + operationId: 'getFileSource', tags: [filesTag.name], summary: 'Get file contents', request: { @@ -179,6 +198,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'post', path: '/api/tree', + operationId: 'getFileTree', tags: [filesTag.name], summary: 'Get a file tree', request: { @@ -201,6 +221,7 @@ export function createPublicOpenApiDocument(version: string) { registry.registerPath({ method: 'post', path: '/api/files', + operationId: 'listFiles', tags: [filesTag.name], summary: 'List files in a repository revision', request: { @@ -227,16 +248,26 @@ export function createPublicOpenApiDocument(version: string) { info: { title: 'Sourcebot Public API', version, - description: 'OpenAPI description for the public Sourcebot REST endpoints used for search, repository listing, and file browsing.', + description: 'OpenAPI description for the public Sourcebot REST endpoints used for search, repository listing, and file browsing. Authentication is instance-dependent: API keys are the standard integration mechanism, OAuth bearer tokens are EE-only, and some instances may allow anonymous access.', }, + security: [ + { bearerAuth: [] }, + { sourcebotApiKey: [] }, + {}, + ], tags: [searchTag, reposTag, filesTag, miscTag], }); - document.components = document.components ?? {}; - document.components.schemas = { - ...(document.components.schemas ?? {}), + const components: ComponentsObject = document.components ?? {}; + components.schemas = { + ...(components.schemas ?? {}), PublicFileTreeNode: publicFileTreeNodeSchema, }; + components.securitySchemes = { + ...(components.securitySchemes ?? {}), + ...securitySchemes, + }; + document.components = components; return document; } diff --git a/packages/web/src/openapi/publicApiSchemas.ts b/packages/web/src/openapi/publicApiSchemas.ts index bfcb60292..632ead9f4 100644 --- a/packages/web/src/openapi/publicApiSchemas.ts +++ b/packages/web/src/openapi/publicApiSchemas.ts @@ -41,7 +41,6 @@ export const publicFileSourceResponseSchema = fileSourceResponseSchema.openapi(' export const publicVersionResponseSchema = z.object({ version: z.string().openapi({ description: 'Running Sourcebot version.', - example: 'v4.15.2', }), }).openapi('PublicVersionResponse');