diff --git a/CLAUDE.md b/CLAUDE.md index 3755abd4c..81f3d1548 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,6 +71,13 @@ className="border-[var(--border)] bg-[var(--card)] text-[var(--foreground)]" ## API Route Handlers +When implementing a new API route, ask the user whether it should be part of the public API. If yes: + +1. Add the request/response Zod schemas to `packages/web/src/openapi/publicApiSchemas.ts`, calling `.openapi('SchemaName')` on each schema to register it with a name. +2. Register the route in `packages/web/src/openapi/publicApiDocument.ts` using `registry.registerPath(...)`, assigning it to the appropriate tag. +3. Add the endpoint to the relevant group in the `API Reference` tab of `docs/docs.json`. +4. Regenerate the OpenAPI spec by running `yarn workspace @sourcebot/web openapi:generate`. + Route handlers should validate inputs using Zod schemas. **Query parameters** (GET requests): diff --git a/docs/api-reference/sourcebot-public.openapi.json b/docs/api-reference/sourcebot-public.openapi.json index ebfa93f1e..e18303a8d 100644 --- a/docs/api-reference/sourcebot-public.openapi.json +++ b/docs/api-reference/sourcebot-public.openapi.json @@ -23,12 +23,8 @@ "description": "System health and version endpoints." }, { - "name": "User Management (EE)", - "description": "User management endpoints. Requires the `org-management` entitlement and OWNER role." - }, - { - "name": "Audit (EE)", - "description": "Audit log endpoints. Requires the `audit` entitlement and OWNER role." + "name": "Enterprise (EE)", + "description": "Enterprise endpoints for user management and audit logging." } ], "security": [ @@ -1100,13 +1096,13 @@ "bearerToken": { "type": "http", "scheme": "bearer", - "description": "Send either a Sourcebot API key (`sbk_...`) or, on EE instances with OAuth enabled, an OAuth access token (`sboa_...`) in the Authorization header." + "description": "Bearer authentication header of the form `Bearer `, where `` is your API key." }, "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-Sourcebot-Api-Key", - "description": "Send a Sourcebot API key (`sbk_...`) in the X-Sourcebot-Api-Key header." + "description": "Header of the form `X-Sourcebot-Api-Key: `, where `` is your API key." } } }, @@ -1828,10 +1824,13 @@ "get": { "operationId": "getUser", "tags": [ - "User Management (EE)" + "Enterprise (EE)" ], "summary": "Get a user", "description": "Fetches profile details for a single organization member by `userId`. Only organization owners can access this endpoint.", + "x-mint": { + "content": "\nThis API is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.\n" + }, "parameters": [ { "schema": { @@ -1900,10 +1899,13 @@ "delete": { "operationId": "deleteUser", "tags": [ - "User Management (EE)" + "Enterprise (EE)" ], "summary": "Delete a user", "description": "Permanently deletes a user and all associated records. Only organization owners can delete other users.", + "x-mint": { + "content": "\nThis API is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.\n" + }, "parameters": [ { "schema": { @@ -1974,10 +1976,13 @@ "get": { "operationId": "listUsers", "tags": [ - "User Management (EE)" + "Enterprise (EE)" ], "summary": "List users", "description": "Returns all members of the organization. Only organization owners can access this endpoint.", + "x-mint": { + "content": "\nThis API is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.\n" + }, "responses": { "200": { "description": "List of organization members.", @@ -2016,10 +2021,13 @@ "get": { "operationId": "listAuditRecords", "tags": [ - "Audit (EE)" + "Enterprise (EE)" ], "summary": "List audit records", "description": "Returns a paginated list of audit log entries. Only organization owners can access this endpoint.", + "x-mint": { + "content": "\nThis API is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it.\n" + }, "parameters": [ { "schema": { diff --git a/docs/docs.json b/docs/docs.json index e526bd312..2899fae8a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -145,6 +145,13 @@ "icon": "code", "openapi": "api-reference/sourcebot-public.openapi.json", "groups": [ + { + "group": "Overview", + "icon": "book-open", + "pages": [ + "docs/api-reference/authentication" + ] + }, { "group": "Search & Navigation", "icon": "magnifying-glass", diff --git a/docs/docs/api-reference/authentication.mdx b/docs/docs/api-reference/authentication.mdx new file mode 100644 index 000000000..9c75fb425 --- /dev/null +++ b/docs/docs/api-reference/authentication.mdx @@ -0,0 +1,34 @@ +--- +title: "Authentication" +--- + +To securely access and interact with Sourcebot’s API, authentication is required. Users must generate an API Key, which will be used to authenticate requests. + + +If [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled, some endpoints will be accessible without a API key. + + +## Creating an API key + +Navigate to **Settings → API Keys** and click **Create API Key**. Copy the value - it is only shown once. + + + API Keys page in Sourcebot Settings + + +## Using an API key + +Pass your API key as a Bearer token in the `Authorization` header on every request. + +```bash +Authorization: Bearer +``` + +For example, to call the `/api/search` endpoint: + +```bash +curl -X POST https://your-sourcebot-instance.com/api/search \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"query": "hello world", "matches": 10}' +``` diff --git a/packages/web/src/openapi/publicApiDocument.ts b/packages/web/src/openapi/publicApiDocument.ts index b6ac5eccb..b6e9c23bf 100644 --- a/packages/web/src/openapi/publicApiDocument.ts +++ b/packages/web/src/openapi/publicApiDocument.ts @@ -31,8 +31,13 @@ const searchTag = { name: 'Search & Navigation', description: 'Code search and s const reposTag = { name: 'Repositories', description: 'Repository listing and metadata endpoints.' }; const gitTag = { name: 'Git', description: 'Git history, diff, and file content endpoints.' }; const systemTag = { name: 'System', description: 'System health and version endpoints.' }; -const eeUserManagementTag = { name: 'User Management (EE)', description: 'User management endpoints. Requires the `org-management` entitlement and OWNER role.' }; -const eeAuditTag = { name: 'Audit (EE)', description: 'Audit log endpoints. Requires the `audit` entitlement and OWNER role.' }; +const eeTag = { name: 'Enterprise (EE)', description: 'Enterprise endpoints for user management and audit logging.' }; + +const EE_LICENSE_KEY_NOTE = dedent` + +This API is only available with an active Enterprise license. Please add your [license key](/docs/license-key) to activate it. + +`; const publicFileTreeNodeSchema: SchemaObject = { type: 'object', @@ -67,13 +72,13 @@ const securitySchemes: Record`, where `` is your API key.', }, [securitySchemeNames.apiKeyHeader]: { type: 'apiKey', in: 'header', name: 'X-Sourcebot-Api-Key', - description: 'Send a Sourcebot API key (`sbk_...`) in the X-Sourcebot-Api-Key header.', + description: 'Header of the form `X-Sourcebot-Api-Key: `, where `` is your API key.', }, }; @@ -339,7 +344,7 @@ export function createPublicOpenApiDocument(version: string) { method: 'get', path: '/api/ee/user', operationId: 'getUser', - tags: [eeUserManagementTag.name], + tags: [eeTag.name], summary: 'Get a user', description: 'Fetches profile details for a single organization member by `userId`. Only organization owners can access this endpoint.', request: { @@ -357,13 +362,16 @@ export function createPublicOpenApiDocument(version: string) { 404: errorJson('User not found.'), 500: errorJson('Unexpected failure.'), }, + 'x-mint': { + content: EE_LICENSE_KEY_NOTE, + }, }); registry.registerPath({ method: 'delete', path: '/api/ee/user', operationId: 'deleteUser', - tags: [eeUserManagementTag.name], + tags: [eeTag.name], summary: 'Delete a user', description: 'Permanently deletes a user and all associated records. Only organization owners can delete other users.', request: { @@ -381,13 +389,16 @@ export function createPublicOpenApiDocument(version: string) { 404: errorJson('User not found.'), 500: errorJson('Unexpected failure.'), }, + 'x-mint': { + content: EE_LICENSE_KEY_NOTE, + }, }); registry.registerPath({ method: 'get', path: '/api/ee/users', operationId: 'listUsers', - tags: [eeUserManagementTag.name], + tags: [eeTag.name], summary: 'List users', description: 'Returns all members of the organization. Only organization owners can access this endpoint.', responses: { @@ -398,6 +409,9 @@ export function createPublicOpenApiDocument(version: string) { 403: errorJson('Insufficient permissions or entitlement not enabled.'), 500: errorJson('Unexpected failure.'), }, + 'x-mint': { + content: EE_LICENSE_KEY_NOTE, + }, }); // EE: Audit @@ -405,7 +419,7 @@ export function createPublicOpenApiDocument(version: string) { method: 'get', path: '/api/ee/audit', operationId: 'listAuditRecords', - tags: [eeAuditTag.name], + tags: [eeTag.name], summary: 'List audit records', description: 'Returns a paginated list of audit log entries. Only organization owners can access this endpoint.', request: { @@ -430,6 +444,9 @@ export function createPublicOpenApiDocument(version: string) { 403: errorJson('Insufficient permissions or entitlement not enabled.'), 500: errorJson('Unexpected failure.'), }, + 'x-mint': { + content: EE_LICENSE_KEY_NOTE, + }, }); const generator = new OpenApiGeneratorV3(registry.definitions); @@ -441,7 +458,7 @@ export function createPublicOpenApiDocument(version: string) { version, 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.', }, - tags: [searchTag, reposTag, gitTag, systemTag, eeUserManagementTag, eeAuditTag], + tags: [searchTag, reposTag, gitTag, systemTag, eeTag], security: [ { [securitySchemeNames.bearerToken]: [] }, { [securitySchemeNames.apiKeyHeader]: [] },