From 9d5b259417f9233e230a6d94bcbe8ac561794c18 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Sun, 10 May 2026 18:07:40 +0100 Subject: [PATCH 1/5] fix(docs): improve questionnaire API SEO metadata --- apps/api/src/main.ts | 13 +- .../questionnaire/questionnaire.controller.ts | 99 ++++++++++++--- packages/docs/openapi.json | 120 +++++++++++++----- .../security-questionnaire-trust-center.mdx | 2 +- 4 files changed, 186 insertions(+), 48 deletions(-) diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index d46f1dd99c..ccc1839756 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -153,8 +153,10 @@ async function bootstrap(): Promise { const serverDescription = describeServer(baseUrl); const config = new DocumentBuilder() - .setTitle('API Documentation') - .setDescription('The API documentation for this application') + .setTitle('Comp AI API') + .setDescription( + 'Comp AI API reference for automating compliance workflows, including evidence collection, policies, trust access, tasks, and security questionnaires.', + ) .setVersion('1.0') .addApiKey( { @@ -169,6 +171,13 @@ async function bootstrap(): Promise { .build(); const document: OpenAPIObject = SwaggerModule.createDocument(app, config); + // Keep implementation-only routes out of public Swagger and Mintlify docs. + for (const routePath of Object.keys(document.paths)) { + if (/^\/v\d+\/internal(?:\/|$)/.test(routePath)) { + delete document.paths[routePath]; + } + } + // Setup Swagger UI at /api/docs SwaggerModule.setup('api/docs', app, document, { raw: ['json'], diff --git a/apps/api/src/questionnaire/questionnaire.controller.ts b/apps/api/src/questionnaire/questionnaire.controller.ts index 567d11b110..368cb43859 100644 --- a/apps/api/src/questionnaire/questionnaire.controller.ts +++ b/apps/api/src/questionnaire/questionnaire.controller.ts @@ -13,12 +13,14 @@ import { UseGuards, UseInterceptors, Logger, + applyDecorators, } from '@nestjs/common'; import type { Response } from 'express'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes, + ApiExtension, ApiOkResponse, ApiOperation, ApiProduces, @@ -57,6 +59,13 @@ import { sanitizeErrorMessage, } from '../utils/sse-utils'; +function ApiDocsOperation(summary: string, description: string, slug: string) { + return applyDecorators( + ApiOperation({ summary, description }), + ApiExtension('x-mint', { href: `/api-reference/questionnaire/${slug}` }), + ); +} + @ApiTags('Questionnaire') @Controller({ path: 'questionnaire', @@ -74,7 +83,11 @@ export class QuestionnaireController { @Get() @RequirePermission('questionnaire', 'read') - @ApiOperation({ summary: 'List questionnaires' }) + @ApiDocsOperation( + 'List questionnaires', + 'List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.', + 'list-questionnaires', + ) @ApiOkResponse({ description: 'List of questionnaires' }) async findAll( @OrganizationId() organizationId: string, @@ -97,7 +110,11 @@ export class QuestionnaireController { @Get(':id') @RequirePermission('questionnaire', 'read') - @ApiOperation({ summary: 'Get a questionnaire by ID' }) + @ApiDocsOperation( + 'Get questionnaire details', + 'Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.', + 'get-a-questionnaire-by-id', + ) @ApiOkResponse({ description: 'Questionnaire details' }) async findById( @Param('id') id: string, @@ -126,7 +143,11 @@ export class QuestionnaireController { @Delete(':id') @RequirePermission('questionnaire', 'delete') - @ApiOperation({ summary: 'Delete a questionnaire' }) + @ApiDocsOperation( + 'Delete a questionnaire', + 'Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.', + 'delete-a-questionnaire', + ) @ApiOkResponse({ description: 'Questionnaire deleted' }) async deleteById( @Param('id') id: string, @@ -137,7 +158,11 @@ export class QuestionnaireController { @Post('parse') @RequirePermission('questionnaire', 'read') - @ApiOperation({ summary: 'Parse an uploaded questionnaire file' }) + @ApiDocsOperation( + 'Parse questionnaire content', + 'Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.', + 'parse-an-uploaded-questionnaire-file', + ) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Parsed questionnaire content', @@ -151,7 +176,11 @@ export class QuestionnaireController { @Post('answer-single') @RequirePermission('questionnaire', 'update') - @ApiOperation({ summary: 'Answer a single questionnaire question' }) + @ApiDocsOperation( + 'Answer one question', + 'Generate an answer for one security questionnaire item using the organization evidence library and return source references.', + 'answer-a-single-questionnaire-question', + ) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Generated single answer result', @@ -192,7 +221,11 @@ export class QuestionnaireController { @Post('save-answer') @RequirePermission('questionnaire', 'update') - @ApiOperation({ summary: 'Save a questionnaire answer' }) + @ApiDocsOperation( + 'Save questionnaire answer', + 'Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.', + 'save-a-questionnaire-answer', + ) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Save manual or generated answer', @@ -214,7 +247,11 @@ export class QuestionnaireController { @Post('delete-answer') @RequirePermission('questionnaire', 'delete') - @ApiOperation({ summary: 'Delete a questionnaire answer' }) + @ApiDocsOperation( + 'Delete questionnaire answer', + 'Delete a stored questionnaire answer when it should be removed from the active response set.', + 'delete-a-questionnaire-answer', + ) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Delete questionnaire answer', @@ -237,7 +274,11 @@ export class QuestionnaireController { @Post('export') @RequirePermission('questionnaire', 'read') @AuditRead() - @ApiOperation({ summary: 'Export a questionnaire' }) + @ApiDocsOperation( + 'Export a questionnaire', + 'Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.', + 'export-a-questionnaire', + ) @ApiConsumes('application/json') @ApiProduces( 'application/pdf', @@ -266,7 +307,11 @@ export class QuestionnaireController { @Post('upload-and-parse') @RequirePermission('questionnaire', 'create') - @ApiOperation({ summary: 'Upload and parse a questionnaire file' }) + @ApiDocsOperation( + 'Start questionnaire parsing', + 'Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.', + 'upload-and-parse-a-questionnaire-file', + ) @ApiConsumes('application/json') @ApiOkResponse({ description: @@ -289,8 +334,12 @@ export class QuestionnaireController { @Post('upload-and-parse/upload') @RequirePermission('questionnaire', 'create') - @ApiOperation({ summary: 'Upload a questionnaire file and parse its questions' }) @UseInterceptors(FileInterceptor('file')) + @ApiDocsOperation( + 'Upload and parse file', + 'Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.', + 'upload-a-questionnaire-file-and-parse-its-questions', + ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -352,8 +401,12 @@ export class QuestionnaireController { @Post('parse/upload') @RequirePermission('questionnaire', 'create') - @ApiOperation({ summary: 'Upload a questionnaire file and auto-answer with export' }) @UseInterceptors(FileInterceptor('file')) + @ApiDocsOperation( + 'Auto-answer uploaded file', + 'Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.', + 'upload-a-questionnaire-file-and-auto-answer-with-export', + ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -433,8 +486,12 @@ export class QuestionnaireController { @Post('parse/upload/token') @Public() @UseGuards() // Override class-level guards — this endpoint uses token-based auth - @ApiOperation({ summary: 'Upload and auto-answer a questionnaire via trust portal token' }) @UseInterceptors(FileInterceptor('file')) + @ApiDocsOperation( + 'Auto-answer with Trust Access', + 'Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.', + 'upload-and-auto-answer-a-questionnaire-via-trust-portal-token', + ) @ApiConsumes('multipart/form-data') @ApiQuery({ name: 'token', @@ -513,7 +570,11 @@ export class QuestionnaireController { @Post('answers/export') @RequirePermission('questionnaire', 'read') @AuditRead() - @ApiOperation({ summary: 'Export questionnaire answers' }) + @ApiDocsOperation( + 'Export generated answers', + 'Generate and export questionnaire answers from a submitted payload using approved organization evidence.', + 'export-questionnaire-answers', + ) @ApiConsumes('application/json') @ApiProduces( 'application/pdf', @@ -543,8 +604,12 @@ export class QuestionnaireController { @Post('answers/export/upload') @RequirePermission('questionnaire', 'create') - @ApiOperation({ summary: 'Upload a questionnaire file and export auto-generated answers' }) @UseInterceptors(FileInterceptor('file')) + @ApiDocsOperation( + 'Upload and export answers', + 'Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.', + 'upload-a-questionnaire-file-and-export-auto-generated-answers', + ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -610,7 +675,11 @@ export class QuestionnaireController { @Post('auto-answer') @RequirePermission('questionnaire', 'update') - @ApiOperation({ summary: 'Auto-answer a questionnaire' }) + @ApiDocsOperation( + 'Stream generated answers', + 'Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.', + 'auto-answer-a-questionnaire', + ) @ApiConsumes('application/json') @ApiProduces('text/event-stream') async autoAnswer( diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 7b7d0c913b..597999e8b9 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -14343,7 +14343,11 @@ "summary": "List questionnaires", "tags": [ "Questionnaire" - ] + ], + "description": "List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.", + "x-mint": { + "href": "/api-reference/questionnaire/list-questionnaires" + } } }, "/v1/questionnaire/{id}": { @@ -14369,10 +14373,14 @@ "apikey": [] } ], - "summary": "Get a questionnaire by ID", + "summary": "Get questionnaire details", "tags": [ "Questionnaire" - ] + ], + "description": "Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.", + "x-mint": { + "href": "/api-reference/questionnaire/get-a-questionnaire-by-id" + } }, "delete": { "operationId": "QuestionnaireController_deleteById_v1", @@ -14399,7 +14407,11 @@ "summary": "Delete a questionnaire", "tags": [ "Questionnaire" - ] + ], + "description": "Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.", + "x-mint": { + "href": "/api-reference/questionnaire/delete-a-questionnaire" + } } }, "/v1/questionnaire/parse": { @@ -14433,10 +14445,14 @@ "apikey": [] } ], - "summary": "Parse an uploaded questionnaire file", + "summary": "Parse questionnaire content", "tags": [ "Questionnaire" - ] + ], + "description": "Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.", + "x-mint": { + "href": "/api-reference/questionnaire/parse-an-uploaded-questionnaire-file" + } } }, "/v1/questionnaire/answer-single": { @@ -14500,10 +14516,14 @@ "apikey": [] } ], - "summary": "Answer a single questionnaire question", + "summary": "Answer one question", "tags": [ "Questionnaire" - ] + ], + "description": "Generate an answer for one security questionnaire item using the organization evidence library and return source references.", + "x-mint": { + "href": "/api-reference/questionnaire/answer-a-single-questionnaire-question" + } } }, "/v1/questionnaire/save-answer": { @@ -14546,10 +14566,14 @@ "apikey": [] } ], - "summary": "Save a questionnaire answer", + "summary": "Save questionnaire answer", "tags": [ "Questionnaire" - ] + ], + "description": "Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.", + "x-mint": { + "href": "/api-reference/questionnaire/save-a-questionnaire-answer" + } } }, "/v1/questionnaire/delete-answer": { @@ -14592,10 +14616,14 @@ "apikey": [] } ], - "summary": "Delete a questionnaire answer", + "summary": "Delete questionnaire answer", "tags": [ "Questionnaire" - ] + ], + "description": "Delete a stored questionnaire answer when it should be removed from the active response set.", + "x-mint": { + "href": "/api-reference/questionnaire/delete-a-questionnaire-answer" + } } }, "/v1/questionnaire/export": { @@ -14625,7 +14653,11 @@ "summary": "Export a questionnaire", "tags": [ "Questionnaire" - ] + ], + "description": "Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.", + "x-mint": { + "href": "/api-reference/questionnaire/export-a-questionnaire" + } } }, "/v1/questionnaire/upload-and-parse": { @@ -14667,10 +14699,14 @@ "apikey": [] } ], - "summary": "Upload and parse a questionnaire file", + "summary": "Start questionnaire parsing", "tags": [ "Questionnaire" - ] + ], + "description": "Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.", + "x-mint": { + "href": "/api-reference/questionnaire/upload-and-parse-a-questionnaire-file" + } } }, "/v1/questionnaire/upload-and-parse/upload": { @@ -14736,10 +14772,14 @@ "apikey": [] } ], - "summary": "Upload a questionnaire file and parse its questions", + "summary": "Upload and parse file", "tags": [ "Questionnaire" - ] + ], + "description": "Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.", + "x-mint": { + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions" + } } }, "/v1/questionnaire/parse/upload": { @@ -14800,10 +14840,14 @@ "apikey": [] } ], - "summary": "Upload a questionnaire file and auto-answer with export", + "summary": "Auto-answer uploaded file", "tags": [ "Questionnaire" - ] + ], + "description": "Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.", + "x-mint": { + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export" + } } }, "/v1/questionnaire/parse/upload/token": { @@ -14860,10 +14904,14 @@ "apikey": [] } ], - "summary": "Upload and auto-answer a questionnaire via trust portal token", + "summary": "Auto-answer with Trust Access", "tags": [ "Questionnaire" - ] + ], + "description": "Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.", + "x-mint": { + "href": "/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token" + } } }, "/v1/questionnaire/answers/export": { @@ -14890,10 +14938,14 @@ "apikey": [] } ], - "summary": "Export questionnaire answers", + "summary": "Export generated answers", "tags": [ "Questionnaire" - ] + ], + "description": "Generate and export questionnaire answers from a submitted payload using approved organization evidence.", + "x-mint": { + "href": "/api-reference/questionnaire/export-questionnaire-answers" + } } }, "/v1/questionnaire/answers/export/upload": { @@ -14945,10 +14997,14 @@ "apikey": [] } ], - "summary": "Upload a questionnaire file and export auto-generated answers", + "summary": "Upload and export answers", "tags": [ "Questionnaire" - ] + ], + "description": "Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.", + "x-mint": { + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers" + } } }, "/v1/questionnaire/auto-answer": { @@ -14975,10 +15031,14 @@ "apikey": [] } ], - "summary": "Auto-answer a questionnaire", + "summary": "Stream generated answers", "tags": [ "Questionnaire" - ] + ], + "description": "Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.", + "x-mint": { + "href": "/api-reference/questionnaire/auto-answer-a-questionnaire" + } } }, "/v1/knowledge-base/documents": { @@ -21775,8 +21835,8 @@ } }, "info": { - "title": "API Documentation", - "description": "The API documentation for this application", + "title": "Comp AI API", + "description": "Comp AI API reference for automating compliance workflows, including evidence collection, policies, trust access, tasks, and security questionnaires.", "version": "1.0", "contact": {} }, @@ -26687,4 +26747,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/docs/security-questionnaire-trust-center.mdx b/packages/docs/security-questionnaire-trust-center.mdx index c400ce8b30..0d56cd45aa 100644 --- a/packages/docs/security-questionnaire-trust-center.mdx +++ b/packages/docs/security-questionnaire-trust-center.mdx @@ -505,7 +505,7 @@ a.click(); For additional assistance with the Security Questionnaire Trust Center API: 1. **Documentation:** Review the [Trust Access documentation](./trust-access) for token management -2. **API Reference:** Check the [API documentation](/api) for detailed endpoint specifications +2. **API Reference:** Check the [questionnaire API reference](/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token) for detailed endpoint specifications 3. **Support:** Contact support at [support@trycomp.ai](mailto:support@trycomp.ai) 4. **Community:** Join our [Discord community](https://discord.gg/compai) for peer support From a0e0de520540a371c95725d524022422bbdd9f3c Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Sun, 10 May 2026 18:22:57 +0100 Subject: [PATCH 2/5] docs: add Mintlify API overview and metadata layer --- apps/api/src/main.ts | 18 +- apps/api/src/openapi/public-docs-metadata.ts | 198 ++++++++++++++++++ .../questionnaire/questionnaire.controller.ts | 99 ++------- packages/docs/api-reference/overview.mdx | 50 +++++ packages/docs/docs.json | 21 +- packages/docs/introduction.mdx | 6 +- packages/docs/openapi.json | 145 +++++++++++-- 7 files changed, 423 insertions(+), 114 deletions(-) create mode 100644 apps/api/src/openapi/public-docs-metadata.ts create mode 100644 packages/docs/api-reference/overview.mdx diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index ccc1839756..62b5f72c6a 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -8,6 +8,11 @@ import * as express from 'express'; import helmet from 'helmet'; import path from 'path'; import { AppModule } from './app.module'; +import { + applyPublicOpenApiMetadata, + PUBLIC_OPENAPI_DESCRIPTION, + PUBLIC_OPENAPI_TITLE, +} from './openapi/public-docs-metadata'; import { isTrustedOrigin } from './auth/auth.server'; import { adminAuthRateLimiter } from './auth/admin-rate-limit.middleware'; import { originCheckMiddleware } from './auth/origin-check.middleware'; @@ -153,10 +158,8 @@ async function bootstrap(): Promise { const serverDescription = describeServer(baseUrl); const config = new DocumentBuilder() - .setTitle('Comp AI API') - .setDescription( - 'Comp AI API reference for automating compliance workflows, including evidence collection, policies, trust access, tasks, and security questionnaires.', - ) + .setTitle(PUBLIC_OPENAPI_TITLE) + .setDescription(PUBLIC_OPENAPI_DESCRIPTION) .setVersion('1.0') .addApiKey( { @@ -171,12 +174,7 @@ async function bootstrap(): Promise { .build(); const document: OpenAPIObject = SwaggerModule.createDocument(app, config); - // Keep implementation-only routes out of public Swagger and Mintlify docs. - for (const routePath of Object.keys(document.paths)) { - if (/^\/v\d+\/internal(?:\/|$)/.test(routePath)) { - delete document.paths[routePath]; - } - } + applyPublicOpenApiMetadata(document); // Setup Swagger UI at /api/docs SwaggerModule.setup('api/docs', app, document, { diff --git a/apps/api/src/openapi/public-docs-metadata.ts b/apps/api/src/openapi/public-docs-metadata.ts new file mode 100644 index 0000000000..a4f2023eb4 --- /dev/null +++ b/apps/api/src/openapi/public-docs-metadata.ts @@ -0,0 +1,198 @@ +import type { OpenAPIObject } from '@nestjs/swagger'; + +export const PUBLIC_OPENAPI_TITLE = 'Comp AI API'; + +export const PUBLIC_OPENAPI_DESCRIPTION = + 'Comp AI API reference for automating compliance workflows, including evidence collection, policies, trust access, tasks, and security questionnaires.'; + +type PublicOperationMetadata = { + summary: string; + description: string; + href: string; + sidebarTitle?: string; + visibility?: 'public' | 'hidden' | 'excluded'; + content?: string; + codeSamples?: Array<{ + lang: string; + label?: string; + source: string; + }>; +}; + +type OpenApiOperation = { + operationId?: string; + summary?: string; + description?: string; + [key: string]: unknown; +}; + +const QUESTIONNAIRE_OPERATION_METADATA: Record< + string, + PublicOperationMetadata +> = { + QuestionnaireController_findAll_v1: { + summary: 'List questionnaires', + description: + 'List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.', + href: '/api-reference/questionnaire/list-questionnaires', + }, + QuestionnaireController_findById_v1: { + summary: 'Get questionnaire details', + description: + 'Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.', + href: '/api-reference/questionnaire/get-a-questionnaire-by-id', + }, + QuestionnaireController_deleteById_v1: { + summary: 'Delete a questionnaire', + description: + 'Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.', + href: '/api-reference/questionnaire/delete-a-questionnaire', + }, + QuestionnaireController_parseQuestionnaire_v1: { + summary: 'Parse questionnaire content', + description: + 'Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.', + href: '/api-reference/questionnaire/parse-an-uploaded-questionnaire-file', + }, + QuestionnaireController_answerSingleQuestion_v1: { + summary: 'Answer one question', + description: + 'Generate an answer for one security questionnaire item using the organization evidence library and return source references.', + href: '/api-reference/questionnaire/answer-a-single-questionnaire-question', + }, + QuestionnaireController_saveAnswer_v1: { + summary: 'Save questionnaire answer', + description: + 'Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.', + href: '/api-reference/questionnaire/save-a-questionnaire-answer', + }, + QuestionnaireController_deleteAnswer_v1: { + summary: 'Delete questionnaire answer', + description: + 'Delete a stored questionnaire answer when it should be removed from the active response set.', + href: '/api-reference/questionnaire/delete-a-questionnaire-answer', + }, + QuestionnaireController_exportById_v1: { + summary: 'Export a questionnaire', + description: + 'Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.', + href: '/api-reference/questionnaire/export-a-questionnaire', + }, + QuestionnaireController_uploadAndParse_v1: { + summary: 'Start questionnaire parsing', + description: + 'Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.', + href: '/api-reference/questionnaire/upload-and-parse-a-questionnaire-file', + }, + QuestionnaireController_uploadAndParseUpload_v1: { + summary: 'Upload and parse file', + description: + 'Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions', + }, + QuestionnaireController_parseQuestionnaireUpload_v1: { + summary: 'Auto-answer uploaded file', + description: + 'Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export', + }, + QuestionnaireController_parseQuestionnaireUploadByToken_v1: { + summary: 'Auto-answer with Trust Access', + description: + 'Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.', + href: '/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token', + sidebarTitle: 'Trust Access auto-answer', + content: + 'Use this endpoint when an external reviewer has an active Trust Access token and needs to upload a security questionnaire for automated answering. Comp AI validates the token, generates answers from approved Trust Center evidence, and returns a ZIP with completed PDF, CSV, and XLSX exports.', + codeSamples: [ + { + lang: 'bash', + label: 'Upload a questionnaire with a Trust Access token', + source: + 'curl --request POST --url "https://api.trycomp.ai/v1/questionnaire/parse/upload/token?token=$TRUST_ACCESS_TOKEN" --form "file=@security-questionnaire.xlsx"', + }, + ], + }, + QuestionnaireController_autoAnswerAndExport_v1: { + summary: 'Export generated answers', + description: + 'Generate and export questionnaire answers from a submitted payload using approved organization evidence.', + href: '/api-reference/questionnaire/export-questionnaire-answers', + }, + QuestionnaireController_autoAnswerAndExportUpload_v1: { + summary: 'Upload and export answers', + description: + 'Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers', + }, + QuestionnaireController_autoAnswer_v1: { + summary: 'Stream generated answers', + description: + 'Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.', + href: '/api-reference/questionnaire/auto-answer-a-questionnaire', + }, +}; + +export const PUBLIC_OPERATION_METADATA: Record< + string, + PublicOperationMetadata +> = { + ...QUESTIONNAIRE_OPERATION_METADATA, +}; + +export function applyPublicOpenApiMetadata(document: OpenAPIObject): void { + document.info.title = PUBLIC_OPENAPI_TITLE; + document.info.description = PUBLIC_OPENAPI_DESCRIPTION; + + const paths = document.paths as Record< + string, + Record + >; + + for (const routePath of Object.keys(paths)) { + if (/^\/v\d+\/internal(?:\/|$)/.test(routePath)) { + delete paths[routePath]; + } + } + + for (const methods of Object.values(paths)) { + for (const operation of Object.values(methods)) { + if ( + !operation || + typeof operation !== 'object' || + !operation.operationId + ) { + continue; + } + + const metadata = PUBLIC_OPERATION_METADATA[operation.operationId]; + if (!metadata) { + continue; + } + + operation.summary = metadata.summary; + operation.description = metadata.description; + operation['x-mint'] = { + href: metadata.href, + metadata: { + title: metadata.summary, + sidebarTitle: metadata.sidebarTitle ?? metadata.summary, + description: metadata.description, + 'og:title': metadata.summary, + 'og:description': metadata.description, + }, + ...(metadata.content && { content: metadata.content }), + }; + + if (metadata.visibility === 'hidden') { + operation['x-hidden'] = true; + } else if (metadata.visibility === 'excluded') { + operation['x-excluded'] = true; + } + + if (metadata.codeSamples) { + operation['x-codeSamples'] = metadata.codeSamples; + } + } + } +} diff --git a/apps/api/src/questionnaire/questionnaire.controller.ts b/apps/api/src/questionnaire/questionnaire.controller.ts index 368cb43859..567d11b110 100644 --- a/apps/api/src/questionnaire/questionnaire.controller.ts +++ b/apps/api/src/questionnaire/questionnaire.controller.ts @@ -13,14 +13,12 @@ import { UseGuards, UseInterceptors, Logger, - applyDecorators, } from '@nestjs/common'; import type { Response } from 'express'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes, - ApiExtension, ApiOkResponse, ApiOperation, ApiProduces, @@ -59,13 +57,6 @@ import { sanitizeErrorMessage, } from '../utils/sse-utils'; -function ApiDocsOperation(summary: string, description: string, slug: string) { - return applyDecorators( - ApiOperation({ summary, description }), - ApiExtension('x-mint', { href: `/api-reference/questionnaire/${slug}` }), - ); -} - @ApiTags('Questionnaire') @Controller({ path: 'questionnaire', @@ -83,11 +74,7 @@ export class QuestionnaireController { @Get() @RequirePermission('questionnaire', 'read') - @ApiDocsOperation( - 'List questionnaires', - 'List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.', - 'list-questionnaires', - ) + @ApiOperation({ summary: 'List questionnaires' }) @ApiOkResponse({ description: 'List of questionnaires' }) async findAll( @OrganizationId() organizationId: string, @@ -110,11 +97,7 @@ export class QuestionnaireController { @Get(':id') @RequirePermission('questionnaire', 'read') - @ApiDocsOperation( - 'Get questionnaire details', - 'Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.', - 'get-a-questionnaire-by-id', - ) + @ApiOperation({ summary: 'Get a questionnaire by ID' }) @ApiOkResponse({ description: 'Questionnaire details' }) async findById( @Param('id') id: string, @@ -143,11 +126,7 @@ export class QuestionnaireController { @Delete(':id') @RequirePermission('questionnaire', 'delete') - @ApiDocsOperation( - 'Delete a questionnaire', - 'Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.', - 'delete-a-questionnaire', - ) + @ApiOperation({ summary: 'Delete a questionnaire' }) @ApiOkResponse({ description: 'Questionnaire deleted' }) async deleteById( @Param('id') id: string, @@ -158,11 +137,7 @@ export class QuestionnaireController { @Post('parse') @RequirePermission('questionnaire', 'read') - @ApiDocsOperation( - 'Parse questionnaire content', - 'Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.', - 'parse-an-uploaded-questionnaire-file', - ) + @ApiOperation({ summary: 'Parse an uploaded questionnaire file' }) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Parsed questionnaire content', @@ -176,11 +151,7 @@ export class QuestionnaireController { @Post('answer-single') @RequirePermission('questionnaire', 'update') - @ApiDocsOperation( - 'Answer one question', - 'Generate an answer for one security questionnaire item using the organization evidence library and return source references.', - 'answer-a-single-questionnaire-question', - ) + @ApiOperation({ summary: 'Answer a single questionnaire question' }) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Generated single answer result', @@ -221,11 +192,7 @@ export class QuestionnaireController { @Post('save-answer') @RequirePermission('questionnaire', 'update') - @ApiDocsOperation( - 'Save questionnaire answer', - 'Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.', - 'save-a-questionnaire-answer', - ) + @ApiOperation({ summary: 'Save a questionnaire answer' }) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Save manual or generated answer', @@ -247,11 +214,7 @@ export class QuestionnaireController { @Post('delete-answer') @RequirePermission('questionnaire', 'delete') - @ApiDocsOperation( - 'Delete questionnaire answer', - 'Delete a stored questionnaire answer when it should be removed from the active response set.', - 'delete-a-questionnaire-answer', - ) + @ApiOperation({ summary: 'Delete a questionnaire answer' }) @ApiConsumes('application/json') @ApiOkResponse({ description: 'Delete questionnaire answer', @@ -274,11 +237,7 @@ export class QuestionnaireController { @Post('export') @RequirePermission('questionnaire', 'read') @AuditRead() - @ApiDocsOperation( - 'Export a questionnaire', - 'Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.', - 'export-a-questionnaire', - ) + @ApiOperation({ summary: 'Export a questionnaire' }) @ApiConsumes('application/json') @ApiProduces( 'application/pdf', @@ -307,11 +266,7 @@ export class QuestionnaireController { @Post('upload-and-parse') @RequirePermission('questionnaire', 'create') - @ApiDocsOperation( - 'Start questionnaire parsing', - 'Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.', - 'upload-and-parse-a-questionnaire-file', - ) + @ApiOperation({ summary: 'Upload and parse a questionnaire file' }) @ApiConsumes('application/json') @ApiOkResponse({ description: @@ -334,12 +289,8 @@ export class QuestionnaireController { @Post('upload-and-parse/upload') @RequirePermission('questionnaire', 'create') + @ApiOperation({ summary: 'Upload a questionnaire file and parse its questions' }) @UseInterceptors(FileInterceptor('file')) - @ApiDocsOperation( - 'Upload and parse file', - 'Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.', - 'upload-a-questionnaire-file-and-parse-its-questions', - ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -401,12 +352,8 @@ export class QuestionnaireController { @Post('parse/upload') @RequirePermission('questionnaire', 'create') + @ApiOperation({ summary: 'Upload a questionnaire file and auto-answer with export' }) @UseInterceptors(FileInterceptor('file')) - @ApiDocsOperation( - 'Auto-answer uploaded file', - 'Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.', - 'upload-a-questionnaire-file-and-auto-answer-with-export', - ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -486,12 +433,8 @@ export class QuestionnaireController { @Post('parse/upload/token') @Public() @UseGuards() // Override class-level guards — this endpoint uses token-based auth + @ApiOperation({ summary: 'Upload and auto-answer a questionnaire via trust portal token' }) @UseInterceptors(FileInterceptor('file')) - @ApiDocsOperation( - 'Auto-answer with Trust Access', - 'Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.', - 'upload-and-auto-answer-a-questionnaire-via-trust-portal-token', - ) @ApiConsumes('multipart/form-data') @ApiQuery({ name: 'token', @@ -570,11 +513,7 @@ export class QuestionnaireController { @Post('answers/export') @RequirePermission('questionnaire', 'read') @AuditRead() - @ApiDocsOperation( - 'Export generated answers', - 'Generate and export questionnaire answers from a submitted payload using approved organization evidence.', - 'export-questionnaire-answers', - ) + @ApiOperation({ summary: 'Export questionnaire answers' }) @ApiConsumes('application/json') @ApiProduces( 'application/pdf', @@ -604,12 +543,8 @@ export class QuestionnaireController { @Post('answers/export/upload') @RequirePermission('questionnaire', 'create') + @ApiOperation({ summary: 'Upload a questionnaire file and export auto-generated answers' }) @UseInterceptors(FileInterceptor('file')) - @ApiDocsOperation( - 'Upload and export answers', - 'Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.', - 'upload-a-questionnaire-file-and-export-auto-generated-answers', - ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -675,11 +610,7 @@ export class QuestionnaireController { @Post('auto-answer') @RequirePermission('questionnaire', 'update') - @ApiDocsOperation( - 'Stream generated answers', - 'Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.', - 'auto-answer-a-questionnaire', - ) + @ApiOperation({ summary: 'Auto-answer a questionnaire' }) @ApiConsumes('application/json') @ApiProduces('text/event-stream') async autoAnswer( diff --git a/packages/docs/api-reference/overview.mdx b/packages/docs/api-reference/overview.mdx new file mode 100644 index 0000000000..603f5a23e7 --- /dev/null +++ b/packages/docs/api-reference/overview.mdx @@ -0,0 +1,50 @@ +--- +title: 'Comp AI API' +description: 'Automate compliance workflows with the Comp AI API, including evidence collection, policies, Trust Access, tasks, and security questionnaires.' +--- + +The Comp AI API helps teams connect compliance workflows to their own systems. Use it to build internal tools, automate evidence collection, manage questionnaires, and connect Trust Center access flows to the rest of your security program. + +## Authentication + +Comp AI API requests use the `X-API-Key` header. Create and manage API keys inside your Comp AI organization, then keep keys server-side and scoped to the workflow you are automating. + +```bash +curl https://api.trycomp.ai/v1/organization \ + -H "X-API-Key: $COMP_AI_API_KEY" +``` + +## Common API Workflows + + + + Upload questionnaires, extract security questions, generate answers from approved evidence, and export reviewer-ready files. + + + + Read organization details and build internal admin workflows around Comp AI account data. + + + + Connect API workflows to Trust Center access requests, reviewer tokens, and shared compliance + resources. + + + + Understand how Comp AI extracts questions and generates answers from published policies and knowledge base content. + + + +## Security Questionnaire Automation + +The highest-leverage API workflow is security questionnaire automation. Comp AI can parse uploaded questionnaires, generate answers from approved organization evidence, and export completed files in common review formats. + +For external reviewers who use Trust Access tokens, use the token-based upload endpoint. For authenticated organization workflows, use the standard questionnaire endpoints with an API key. + +## Public API Reference + +The API reference is generated from the same NestJS OpenAPI specification used by the Comp AI API. Endpoint titles, descriptions, and visibility are curated in the API source so generated Mintlify pages stay accurate when the API changes. diff --git a/packages/docs/docs.json b/packages/docs/docs.json index 3f433909f3..172b7de46e 100644 --- a/packages/docs/docs.json +++ b/packages/docs/docs.json @@ -84,7 +84,16 @@ }, { "tab": "API", - "openapi": { "source": "./openapi.json" } + "groups": [ + { + "group": "Overview", + "pages": ["api-reference/overview"] + }, + { + "group": "Reference", + "openapi": { "source": "./openapi.json" } + } + ] } ], "global": { @@ -114,6 +123,16 @@ "href": "https://trycomp.ai" } }, + "api": { + "playground": { + "display": "interactive" + }, + "examples": { + "languages": ["bash", "javascript", "python"], + "defaults": "required", + "prefill": true + } + }, "footer": { "socials": { "x": "https://x.com/trycompai", diff --git a/packages/docs/introduction.mdx b/packages/docs/introduction.mdx index 64c49cd4d3..0231c4156c 100644 --- a/packages/docs/introduction.mdx +++ b/packages/docs/introduction.mdx @@ -21,7 +21,7 @@ Resource links for Comp AI features Comp AI API reference docs - - Build internal tools to help manage evidence collection, policies, employees and more with the Comp AI API + + Build internal tools for evidence collection, policies, questionnaires, Trust Access, and compliance automation with the Comp AI API - \ No newline at end of file + diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 597999e8b9..ffca0ac700 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -14346,7 +14346,14 @@ ], "description": "List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.", "x-mint": { - "href": "/api-reference/questionnaire/list-questionnaires" + "href": "/api-reference/questionnaire/list-questionnaires", + "metadata": { + "title": "List questionnaires", + "sidebarTitle": "List questionnaires", + "description": "List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.", + "og:title": "List questionnaires", + "og:description": "List all security questionnaires for an organization with auth context for API clients and compliance automation workflows." + } } } }, @@ -14379,7 +14386,14 @@ ], "description": "Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.", "x-mint": { - "href": "/api-reference/questionnaire/get-a-questionnaire-by-id" + "href": "/api-reference/questionnaire/get-a-questionnaire-by-id", + "metadata": { + "title": "Get questionnaire details", + "sidebarTitle": "Get questionnaire details", + "description": "Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.", + "og:title": "Get questionnaire details", + "og:description": "Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client." + } } }, "delete": { @@ -14410,7 +14424,14 @@ ], "description": "Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.", "x-mint": { - "href": "/api-reference/questionnaire/delete-a-questionnaire" + "href": "/api-reference/questionnaire/delete-a-questionnaire", + "metadata": { + "title": "Delete a questionnaire", + "sidebarTitle": "Delete a questionnaire", + "description": "Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.", + "og:title": "Delete a questionnaire", + "og:description": "Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows." + } } } }, @@ -14451,7 +14472,14 @@ ], "description": "Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.", "x-mint": { - "href": "/api-reference/questionnaire/parse-an-uploaded-questionnaire-file" + "href": "/api-reference/questionnaire/parse-an-uploaded-questionnaire-file", + "metadata": { + "title": "Parse questionnaire content", + "sidebarTitle": "Parse questionnaire content", + "description": "Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.", + "og:title": "Parse questionnaire content", + "og:description": "Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers." + } } } }, @@ -14522,7 +14550,14 @@ ], "description": "Generate an answer for one security questionnaire item using the organization evidence library and return source references.", "x-mint": { - "href": "/api-reference/questionnaire/answer-a-single-questionnaire-question" + "href": "/api-reference/questionnaire/answer-a-single-questionnaire-question", + "metadata": { + "title": "Answer one question", + "sidebarTitle": "Answer one question", + "description": "Generate an answer for one security questionnaire item using the organization evidence library and return source references.", + "og:title": "Answer one question", + "og:description": "Generate an answer for one security questionnaire item using the organization evidence library and return source references." + } } } }, @@ -14572,7 +14607,14 @@ ], "description": "Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.", "x-mint": { - "href": "/api-reference/questionnaire/save-a-questionnaire-answer" + "href": "/api-reference/questionnaire/save-a-questionnaire-answer", + "metadata": { + "title": "Save questionnaire answer", + "sidebarTitle": "Save questionnaire answer", + "description": "Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.", + "og:title": "Save questionnaire answer", + "og:description": "Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking." + } } } }, @@ -14622,7 +14664,14 @@ ], "description": "Delete a stored questionnaire answer when it should be removed from the active response set.", "x-mint": { - "href": "/api-reference/questionnaire/delete-a-questionnaire-answer" + "href": "/api-reference/questionnaire/delete-a-questionnaire-answer", + "metadata": { + "title": "Delete questionnaire answer", + "sidebarTitle": "Delete questionnaire answer", + "description": "Delete a stored questionnaire answer when it should be removed from the active response set.", + "og:title": "Delete questionnaire answer", + "og:description": "Delete a stored questionnaire answer when it should be removed from the active response set." + } } } }, @@ -14656,7 +14705,14 @@ ], "description": "Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.", "x-mint": { - "href": "/api-reference/questionnaire/export-a-questionnaire" + "href": "/api-reference/questionnaire/export-a-questionnaire", + "metadata": { + "title": "Export a questionnaire", + "sidebarTitle": "Export a questionnaire", + "description": "Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.", + "og:title": "Export a questionnaire", + "og:description": "Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews." + } } } }, @@ -14705,7 +14761,14 @@ ], "description": "Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.", "x-mint": { - "href": "/api-reference/questionnaire/upload-and-parse-a-questionnaire-file" + "href": "/api-reference/questionnaire/upload-and-parse-a-questionnaire-file", + "metadata": { + "title": "Start questionnaire parsing", + "sidebarTitle": "Start questionnaire parsing", + "description": "Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.", + "og:title": "Start questionnaire parsing", + "og:description": "Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking." + } } } }, @@ -14778,7 +14841,14 @@ ], "description": "Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.", "x-mint": { - "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions" + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions", + "metadata": { + "title": "Upload and parse file", + "sidebarTitle": "Upload and parse file", + "description": "Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.", + "og:title": "Upload and parse file", + "og:description": "Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count." + } } } }, @@ -14846,7 +14916,14 @@ ], "description": "Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.", "x-mint": { - "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export" + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export", + "metadata": { + "title": "Auto-answer uploaded file", + "sidebarTitle": "Auto-answer uploaded file", + "description": "Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.", + "og:title": "Auto-answer uploaded file", + "og:description": "Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format." + } } } }, @@ -14910,8 +14987,23 @@ ], "description": "Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.", "x-mint": { - "href": "/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token" - } + "href": "/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token", + "metadata": { + "title": "Auto-answer with Trust Access", + "sidebarTitle": "Trust Access auto-answer", + "description": "Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.", + "og:title": "Auto-answer with Trust Access", + "og:description": "Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers." + }, + "content": "Use this endpoint when an external reviewer has an active Trust Access token and needs to upload a security questionnaire for automated answering. Comp AI validates the token, generates answers from approved Trust Center evidence, and returns a ZIP with completed PDF, CSV, and XLSX exports." + }, + "x-codeSamples": [ + { + "lang": "bash", + "label": "Upload a questionnaire with a Trust Access token", + "source": "curl --request POST --url \"https://api.trycomp.ai/v1/questionnaire/parse/upload/token?token=$TRUST_ACCESS_TOKEN\" --form \"file=@security-questionnaire.xlsx\"" + } + ] } }, "/v1/questionnaire/answers/export": { @@ -14944,7 +15036,14 @@ ], "description": "Generate and export questionnaire answers from a submitted payload using approved organization evidence.", "x-mint": { - "href": "/api-reference/questionnaire/export-questionnaire-answers" + "href": "/api-reference/questionnaire/export-questionnaire-answers", + "metadata": { + "title": "Export generated answers", + "sidebarTitle": "Export generated answers", + "description": "Generate and export questionnaire answers from a submitted payload using approved organization evidence.", + "og:title": "Export generated answers", + "og:description": "Generate and export questionnaire answers from a submitted payload using approved organization evidence." + } } } }, @@ -15003,7 +15102,14 @@ ], "description": "Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.", "x-mint": { - "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers" + "href": "/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers", + "metadata": { + "title": "Upload and export answers", + "sidebarTitle": "Upload and export answers", + "description": "Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.", + "og:title": "Upload and export answers", + "og:description": "Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format." + } } } }, @@ -15037,7 +15143,14 @@ ], "description": "Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.", "x-mint": { - "href": "/api-reference/questionnaire/auto-answer-a-questionnaire" + "href": "/api-reference/questionnaire/auto-answer-a-questionnaire", + "metadata": { + "title": "Stream generated answers", + "sidebarTitle": "Stream generated answers", + "description": "Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.", + "og:title": "Stream generated answers", + "og:description": "Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced." + } } } }, From 5c3421289675039e339af24e23878e5e07668fea Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Sun, 10 May 2026 17:07:49 -0400 Subject: [PATCH 3/5] docs(api): broaden public API SEO metadata --- apps/api/src/gen-openapi.spec.ts | 65 +- apps/api/src/openapi-docs.spec.ts | 174 +- apps/api/src/openapi/operation-metadata.ts | 239 + apps/api/src/openapi/public-docs-metadata.ts | 386 +- .../api/src/openapi/questionnaire-metadata.ts | 108 + apps/api/src/openapi/schema-pruning.ts | 81 + apps/api/src/openapi/seo-text.ts | 53 + apps/api/src/openapi/tag-metadata.ts | 190 + apps/api/src/openapi/types.ts | 29 + .../openapi/workflow-operation-metadata.ts | 147 + packages/docs/api-reference/overview.mdx | 90 +- packages/docs/automated-evidence.mdx | 20 +- packages/docs/device-agent.mdx | 63 +- packages/docs/docs.json | 21 +- packages/docs/introduction.mdx | 38 +- packages/docs/openapi.json | 20440 ++++++++-------- packages/docs/penetration-tests.mdx | 22 +- .../security-questionnaire-trust-center.mdx | 223 +- packages/docs/security-questionnaire.mdx | 12 +- packages/docs/trust-access.mdx | 7 +- 20 files changed, 11558 insertions(+), 10850 deletions(-) create mode 100644 apps/api/src/openapi/operation-metadata.ts create mode 100644 apps/api/src/openapi/questionnaire-metadata.ts create mode 100644 apps/api/src/openapi/schema-pruning.ts create mode 100644 apps/api/src/openapi/seo-text.ts create mode 100644 apps/api/src/openapi/tag-metadata.ts create mode 100644 apps/api/src/openapi/types.ts create mode 100644 apps/api/src/openapi/workflow-operation-metadata.ts diff --git a/apps/api/src/gen-openapi.spec.ts b/apps/api/src/gen-openapi.spec.ts index 423ea2bc83..b98d30dda5 100644 --- a/apps/api/src/gen-openapi.spec.ts +++ b/apps/api/src/gen-openapi.spec.ts @@ -1,7 +1,7 @@ // Script-style Jest spec: generates packages/docs/openapi.json using the same // mocks as openapi-docs.spec.ts (no live DB or env vars needed). // Skipped by default to avoid side effects in CI. -// Run manually with: cd apps/api && GEN_OPENAPI=1 npx jest src/gen-openapi.spec.ts +// Run manually with: cd apps/api && GEN_OPENAPI=1 bunx jest src/gen-openapi.spec.ts // Mock better-auth ESM-only modules so Jest (CJS) can import AppModule's transitive AuthModule. jest.mock('./auth/auth.server', () => ({ @@ -21,7 +21,12 @@ jest.mock('@thallesp/nestjs-better-auth', () => { @Module({}) class AuthModuleStub { static forRoot() { - return { module: AuthModuleStub, imports: [], providers: [], exports: [] }; + return { + module: AuthModuleStub, + imports: [], + providers: [], + exports: [], + }; } } return { AuthModule: AuthModuleStub }; @@ -72,6 +77,7 @@ jest.mock('@db', () => { organization: { findFirst: jest.fn(), findMany: jest.fn() }, auditLog: { create: jest.fn() }, trust: { findMany: jest.fn().mockResolvedValue([]) }, + dynamicIntegration: { findMany: jest.fn().mockResolvedValue([]) }, apiKey: { findFirst: jest.fn() }, session: { findFirst: jest.fn() }, member: { findFirst: jest.fn() }, @@ -99,6 +105,12 @@ import { Test } from '@nestjs/testing'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { INestApplication, VersioningType } from '@nestjs/common'; import { AppModule } from './app.module'; +import { + applyPublicOpenApiMetadata, + PUBLIC_OPENAPI_DESCRIPTION, + PUBLIC_OPENAPI_TITLE, + PUBLIC_SERVER_URL, +} from './openapi/public-docs-metadata'; const shouldRun = process.env.GEN_OPENAPI === '1'; const maybeDescribe = shouldRun ? describe : describe.skip; @@ -121,18 +133,9 @@ maybeDescribe('Generate openapi.json', () => { }); it('writes openapi.json without excluded paths', () => { - const baseUrl = process.env.BASE_URL ?? 'http://localhost:3333'; - const serverDescription = baseUrl.includes('api.staging.trycomp.ai') - ? 'Staging API Server' - : baseUrl.includes('api.trycomp.ai') - ? 'Production API Server' - : baseUrl.startsWith('http://localhost') - ? 'Local API Server' - : 'API Server'; - const config = new DocumentBuilder() - .setTitle('API Documentation') - .setDescription('The API documentation for this application') + .setTitle(PUBLIC_OPENAPI_TITLE) + .setDescription(PUBLIC_OPENAPI_DESCRIPTION) .setVersion('1.0') .addApiKey( { @@ -143,10 +146,10 @@ maybeDescribe('Generate openapi.json', () => { }, 'apikey', ) - .addServer(baseUrl, serverDescription) .build(); const document = SwaggerModule.createDocument(app, config); + applyPublicOpenApiMetadata(document); const openapiPath = path.join( __dirname, @@ -161,13 +164,43 @@ maybeDescribe('Generate openapi.json', () => { writeFileSync(openapiPath, JSON.stringify(document, null, 2)); console.log(`OpenAPI documentation written to ${openapiPath}`); + expect(document.servers).toEqual([ + { + url: PUBLIC_SERVER_URL, + description: 'Production API Server', + }, + ]); + // Verify excluded paths are absent - const hiddenPrefixes = ['/v1/auth', '/v1/admin', '/v1/internal']; - for (const prefix of hiddenPrefixes) { + const excludedPrefixes = [ + '/v1/auth', + '/v1/admin', + '/v1/internal', + '/v1/framework-editor', + '/v1/browserbase', + '/v1/assistant-chat', + '/v1/health', + '/v1/email/unsubscribe', + '/v1/integrations/webhooks', + '/v1/secrets', + '/v1/billing', + '/v1/background-check-billing', + '/v1/pentest-credits', + '/v1/finding-template', + ]; + for (const prefix of excludedPrefixes) { const exposed = Object.keys(document.paths).filter((p) => p.startsWith(prefix), ); expect(exposed).toEqual([]); } + + const excludedPathPatterns = [/\/admin(?:\/|$)/, /\/webhooks?(?:\/|$)/]; + for (const pattern of excludedPathPatterns) { + const exposed = Object.keys(document.paths).filter((p) => + pattern.test(p), + ); + expect(exposed).toEqual([]); + } }); }); diff --git a/apps/api/src/openapi-docs.spec.ts b/apps/api/src/openapi-docs.spec.ts index 6317293e92..00e7c3a5d3 100644 --- a/apps/api/src/openapi-docs.spec.ts +++ b/apps/api/src/openapi-docs.spec.ts @@ -1,8 +1,6 @@ // Mock better-auth ESM-only modules so Jest (CJS) can import AppModule's transitive AuthModule. // These must appear before any imports so that Jest hoists them before module evaluation. -// Stub the auth instance so auth.server.ts never runs its top-level side effects -// (validateSecurityConfig, betterAuth(), Redis connection, etc.) jest.mock('./auth/auth.server', () => ({ auth: { api: {}, @@ -14,20 +12,23 @@ jest.mock('./auth/auth.server', () => ({ isStaticTrustedOrigin: () => false, })); -// Stub the NestJS better-auth integration module jest.mock('@thallesp/nestjs-better-auth', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { Module } = require('@nestjs/common'); @Module({}) class AuthModuleStub { static forRoot() { - return { module: AuthModuleStub, imports: [], providers: [], exports: [] }; + return { + module: AuthModuleStub, + imports: [], + providers: [], + exports: [], + }; } } return { AuthModule: AuthModuleStub }; }); -// Stub better-auth ESM-only packages (loaded by @trycompai/auth package) jest.mock('better-auth/plugins/access', () => ({ createAccessControl: () => ({ newRole: () => ({}), @@ -76,6 +77,7 @@ jest.mock('@db', () => { organization: { findFirst: jest.fn(), findMany: jest.fn() }, auditLog: { create: jest.fn() }, trust: { findMany: jest.fn().mockResolvedValue([]) }, + dynamicIntegration: { findMany: jest.fn().mockResolvedValue([]) }, apiKey: { findFirst: jest.fn() }, session: { findFirst: jest.fn() }, member: { findFirst: jest.fn() }, @@ -101,9 +103,19 @@ process.env.APP_AWS_BUCKET_NAME = 'test-bucket'; process.env.APP_AWS_REGION = 'us-east-1'; import { Test } from '@nestjs/testing'; -import { DocumentBuilder, SwaggerModule, type OpenAPIObject } from '@nestjs/swagger'; +import { + DocumentBuilder, + SwaggerModule, + type OpenAPIObject, +} from '@nestjs/swagger'; import { INestApplication, VersioningType } from '@nestjs/common'; import { AppModule } from './app.module'; +import { + applyPublicOpenApiMetadata, + PUBLIC_OPENAPI_DESCRIPTION, + PUBLIC_OPENAPI_TITLE, + PUBLIC_SERVER_URL, +} from './openapi/public-docs-metadata'; describe('OpenAPI document', () => { let app: INestApplication; @@ -119,31 +131,68 @@ describe('OpenAPI document', () => { await app.init(); const config = new DocumentBuilder() - .setTitle('Test') + .setTitle(PUBLIC_OPENAPI_TITLE) + .setDescription(PUBLIC_OPENAPI_DESCRIPTION) .setVersion('1.0') .build(); document = SwaggerModule.createDocument(app, config); + applyPublicOpenApiMetadata(document); }); afterAll(async () => { if (app) await app.close(); }); - const hiddenPrefixes = ['/v1/auth', '/v1/admin', '/v1/internal']; + const excludedPrefixes = [ + '/v1/auth', + '/v1/admin', + '/v1/internal', + '/v1/framework-editor', + '/v1/browserbase', + '/v1/assistant-chat', + '/v1/health', + '/v1/email/unsubscribe', + '/v1/integrations/webhooks', + '/v1/secrets', + '/v1/billing', + '/v1/background-check-billing', + '/v1/pentest-credits', + '/v1/finding-template', + ]; - for (const prefix of hiddenPrefixes) { - it(`does not expose any path starting with ${prefix}`, () => { - const exposed = Object.keys(document.paths).filter((p) => p.startsWith(prefix)); - expect(exposed).toEqual([]); + const excludedPathPatterns = [/\/admin(?:\/|$)/, /\/webhooks?(?:\/|$)/]; + + it('does not expose excluded public docs paths', () => { + const routePaths = Object.keys(document.paths); + const exposed = routePaths.filter( + (path) => + excludedPrefixes.some((prefix) => path.startsWith(prefix)) || + excludedPathPatterns.some((pattern) => pattern.test(path)), + ); + + expect(exposed).toEqual([]); + }); + + describe('public metadata', () => { + it('uses production API servers in the generated Mintlify spec', () => { + expect(document.info.title).toBe(PUBLIC_OPENAPI_TITLE); + expect(document.info.description).toBe(PUBLIC_OPENAPI_DESCRIPTION); + expect(document.servers).toEqual([ + { + url: PUBLIC_SERVER_URL, + description: 'Production API Server', + }, + ]); }); - } - describe('summaries', () => { it('every public operation declares a non-empty summary', () => { const missing: string[] = []; for (const [routePath, methods] of Object.entries(document.paths)) { - for (const [method, op] of Object.entries(methods as Record)) { + for (const [method, op] of Object.entries( + methods as Record, + )) { if (typeof op !== 'object' || !op) continue; + if (op['x-excluded']) continue; if (!op.summary || op.summary.trim() === '') { missing.push(`${method.toUpperCase()} ${routePath}`); } @@ -151,5 +200,100 @@ describe('OpenAPI document', () => { } expect(missing).toEqual([]); }); + + it('every public operation has a description and Mintlify metadata', () => { + const missing: string[] = []; + const invalidSeo: string[] = []; + for (const [routePath, methods] of Object.entries(document.paths)) { + for (const [method, op] of Object.entries( + methods as Record< + string, + { + description?: string; + 'x-excluded'?: true; + 'x-mint'?: { + metadata?: { description?: string; title?: string }; + }; + } + >, + )) { + if (typeof op !== 'object' || !op) continue; + if (op['x-excluded']) continue; + + const metaDescription = op['x-mint']?.metadata?.description; + if (!op.description?.trim() || !metaDescription?.trim()) { + missing.push(`${method.toUpperCase()} ${routePath}`); + } + + const metaTitle = op['x-mint']?.metadata?.title; + if ( + metaDescription && + (metaDescription.length < 80 || + metaDescription.length > 160 || + metaDescription.includes('Use this Comp AI')) + ) { + invalidSeo.push(`${method.toUpperCase()} ${routePath}`); + } + if (metaTitle && metaTitle.length > 60) { + invalidSeo.push(`${method.toUpperCase()} ${routePath}`); + } + } + } + + expect(missing).toEqual([]); + expect(invalidSeo).toEqual([]); + }); + + it('does not retain sensitive hidden tags in the public spec', () => { + const sensitiveTags = [ + 'Background Check Billing', + 'Billing', + 'Finding Templates', + 'Pentest Credits', + 'Secrets', + ]; + const exposedTags = new Set( + Object.values(document.paths).flatMap((methods) => + Object.values(methods as Record) + .flatMap((op) => op.tags ?? []) + .filter((tag) => sensitiveTags.includes(tag)), + ), + ); + + expect([...exposedTags].sort()).toEqual([]); + }); + + it('curates high-value API pages with operation-specific SEO copy', () => { + const upload = document.paths['/v1/questionnaire/parse/upload/token'] + ?.post as + | { + summary?: string; + description?: string; + 'x-codeSamples'?: Array<{ lang: string }>; + 'x-mint'?: { href?: string; metadata?: { title?: string } }; + } + | undefined; + + expect(upload?.summary).toBe('Upload questionnaire with Trust Access'); + expect(upload?.description).toContain('Trust Portal access token'); + expect(upload?.['x-mint']?.href).toBe( + '/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token', + ); + expect(upload?.['x-codeSamples']?.[0]?.lang).toBe('bash'); + + const policies = document.paths['/v1/policies']?.get as + | { + summary?: string; + description?: string; + 'x-mint'?: { metadata?: { title?: string } }; + } + | undefined; + + expect(policies?.summary).toBe('List compliance policies'); + expect(policies?.description).toContain('SOC 2'); + expect(policies?.['x-mint']?.metadata?.title).toBe( + 'List compliance policies | Comp AI API', + ); + }); }); }); diff --git a/apps/api/src/openapi/operation-metadata.ts b/apps/api/src/openapi/operation-metadata.ts new file mode 100644 index 0000000000..04e7112645 --- /dev/null +++ b/apps/api/src/openapi/operation-metadata.ts @@ -0,0 +1,239 @@ +import { QUESTIONNAIRE_OPERATION_METADATA } from './questionnaire-metadata'; +import type { PublicOperationMetadata } from './types'; +import { WORKFLOW_OPERATION_METADATA } from './workflow-operation-metadata'; + +const CORE_OPERATION_METADATA: Record = { + OrganizationController_getOrganization_v1: { + summary: 'Get organization profile', + description: + 'Retrieve organization profile data used to personalize compliance workflows, Trust Center branding, API automation, and audit readiness reporting.', + codeSamples: [ + { + lang: 'bash', + label: 'Get organization profile', + source: + 'curl --request GET --url "https://api.trycomp.ai/v1/organization" --header "X-API-Key: $COMP_AI_API_KEY"', + }, + ], + }, + OrganizationController_listApiKeys_v1: { + summary: 'List API keys', + description: + 'List active API keys for an organization so administrators can audit automation access and rotate credentials safely.', + }, + OrganizationController_createApiKey_v1: { + summary: 'Create API key', + description: + 'Create a scoped API key for server-side compliance automation such as evidence sync, policy workflows, or security questionnaire tooling.', + }, + OrganizationController_getAvailableScopes_v1: { + summary: 'List API key scopes', + description: + 'Retrieve available API key scopes and permissions before creating credentials for a specific compliance automation workflow.', + }, + OrganizationController_revokeApiKey_v1: { + summary: 'Revoke API key', + description: + 'Revoke an organization API key when an integration is retired, credentials rotate, or access should be removed.', + }, + PoliciesController_getAllPolicies_v1: { + summary: 'List compliance policies', + description: + 'List compliance policies for an organization, including drafts and published policies used for SOC 2, ISO 27001, HIPAA, and GDPR workflows.', + codeSamples: [ + { + lang: 'bash', + label: 'List policies', + source: + 'curl --request GET --url "https://api.trycomp.ai/v1/policies" --header "X-API-Key: $COMP_AI_API_KEY"', + }, + ], + }, + PoliciesController_createPolicy_v1: { + summary: 'Create compliance policy', + description: + 'Create a policy record that can be reviewed, versioned, published, linked to controls, and used as source evidence for questionnaires.', + }, + PoliciesController_publishAllPolicies_v1: { + summary: 'Publish all draft policies', + description: + 'Publish draft policies in bulk so approved policy content can power Trust Center sharing, questionnaire answers, and audit evidence.', + }, + PoliciesController_downloadAllPolicies_v1: { + summary: 'Download all published policies', + description: + 'Generate a single PDF bundle of published compliance policies for auditors, customer security reviews, and Trust Center workflows.', + }, + PoliciesController_regeneratePolicy_v1: { + summary: 'Regenerate policy with AI', + description: + 'Regenerate policy content using Comp AI while keeping the result reviewable before it is published or used as compliance evidence.', + }, + PoliciesController_aiChatPolicy_v1: { + summary: 'Chat with AI about a policy', + description: + 'Ask policy-specific questions and request draft improvements while preserving human review before policy changes are applied.', + }, + KnowledgeBaseController_listDocuments_v1: { + summary: 'List knowledge base documents', + description: + 'List uploaded knowledge base documents that Comp AI can use as approved source material for answers, policies, and reviews.', + }, + KnowledgeBaseController_uploadDocument_v1: { + summary: 'Upload knowledge base document', + description: + 'Upload supporting documentation so Comp AI can process approved source material for questionnaire answers and policy workflows.', + }, + KnowledgeBaseController_processDocuments_v1: { + summary: 'Process knowledge base documents', + description: + 'Start document processing so uploaded knowledge base files become searchable source material for AI-assisted compliance workflows.', + }, + KnowledgeBaseController_saveManualAnswer_v1: { + summary: 'Save reusable manual answer', + description: + 'Save or update a reusable manual answer for security questionnaires that need approved, consistent response language.', + }, + TasksController_getTasks_v1: { + summary: 'List compliance tasks', + description: + 'List compliance tasks with assignments and status so teams can track audit readiness, evidence work, and control implementation.', + codeSamples: [ + { + lang: 'bash', + label: 'List tasks', + source: + 'curl --request GET --url "https://api.trycomp.ai/v1/tasks" --header "X-API-Key: $COMP_AI_API_KEY"', + }, + ], + }, + TasksController_createTask_v1: { + summary: 'Create compliance task', + description: + 'Create a compliance task for evidence collection, remediation, review, or recurring control work inside an organization.', + }, + TasksController_uploadTaskAttachment_v1: { + summary: 'Upload task evidence', + description: + 'Upload an evidence attachment to a task so auditors and reviewers can trace completion back to source documentation.', + }, + EvidenceExportController_exportTaskEvidenceZip_v1: { + summary: 'Export task evidence as ZIP', + description: + 'Download a ZIP package containing task evidence and automation results for auditor review or customer security requests.', + }, + AutomationsController_createAutomation_v1: { + summary: 'Create evidence automation', + description: + 'Create an automated evidence workflow attached to a task so Comp AI can collect recurring proof from connected systems.', + }, + TrustAccessController_createAccessRequest_v1: { + summary: 'Submit Trust Access request', + description: + 'Submit an external Trust Center access request with requester details, company context, and review reason for administrator approval.', + }, + TrustAccessController_approveRequest_v1: { + summary: 'Approve Trust Access request', + description: + 'Approve a Trust Center access request, configure the grant window, and start the NDA or access email workflow.', + }, + TrustAccessController_signNda_v1: { + summary: 'Sign Trust Access NDA', + description: + 'Submit a digital NDA signature for a Trust Access token so the requester can receive time-limited access to shared resources.', + }, + TrustAccessController_getGrantByAccessToken_v1: { + summary: 'Get Trust Access grant', + description: + 'Retrieve grant details for a Trust Access token before showing token-scoped policies, documents, questionnaires, and resources.', + }, + TrustAccessController_getPoliciesByAccessToken_v1: { + summary: 'List Trust Access policies', + description: + 'List published policies available to an external reviewer through a valid Trust Access token.', + }, + TrustAccessController_downloadAllPolicies_v1: { + summary: 'Download Trust Access policy bundle', + description: + 'Generate a watermarked PDF bundle of policies available through a Trust Access token for customer security review.', + }, + TrustPortalController_getSettings_v1: { + summary: 'Get Trust Center settings', + description: + 'Retrieve Trust Center settings used to configure public status, custom domains, framework visibility, resources, FAQs, and access rules.', + }, + TrustPortalController_uploadComplianceResource_v1: { + summary: 'Upload compliance certificate', + description: + 'Upload or replace a compliance certificate PDF such as SOC 2, ISO 27001, HIPAA, or GDPR evidence for Trust Center sharing.', + }, + TrustPortalController_updateOverview_v1: { + summary: 'Update Trust Center overview', + description: + 'Update the public Trust Center overview content that explains security posture and compliance status to prospects and customers.', + }, + ConnectionsController_listProviders_v1: { + summary: 'List integration providers', + description: + 'List available integration providers that can connect to the organization for automated evidence collection and compliance checks.', + }, + ConnectionsController_createConnection_v1: { + summary: 'Create integration connection', + description: + 'Create an integration connection so Comp AI can collect evidence, run checks, or sync data from a connected provider.', + }, + ChecksController_runConnectionChecks_v1: { + summary: 'Run integration checks', + description: + 'Run all compliance checks for an integration connection and capture results as automated evidence.', + }, + CloudSecurityController_scan_v1: { + summary: 'Run cloud security scan', + description: + 'Trigger a cloud security scan for a connected AWS, Azure, or GCP account and collect findings for compliance remediation.', + }, + CloudSecurityController_getFindings_v1: { + summary: 'List cloud security findings', + description: + 'List cloud security findings discovered by scans so teams can prioritize remediation before issues become audit findings.', + }, + RemediationController_preview_v1: { + summary: 'Preview cloud remediation', + description: + 'Preview a cloud remediation action before execution so teams can review the intended change and affected resources.', + }, + DeviceAgentController_registerDevice_v1: { + summary: 'Register device agent', + description: + 'Register a Comp AI Device Agent installation so employee endpoint checks can report into compliance tasks and device inventory.', + }, + DeviceAgentController_checkIn_v1: { + summary: 'Submit device compliance check-in', + description: + 'Submit device security check results for encryption, antivirus, password policy, screen lock, and other endpoint controls.', + }, + SecurityPenetrationTestsController_create_v1: { + summary: 'Create penetration test', + description: + 'Create an AI-powered penetration test run for an approved target and track the resulting findings and report artifacts.', + }, + VendorsController_triggerAssessment_v1: { + summary: 'Trigger vendor risk assessment', + description: + 'Trigger a vendor risk assessment so Comp AI can update third-party risk evidence and vendor security review status.', + }, + RisksController_createRisk_v1: { + summary: 'Create organization risk', + description: + 'Create a risk record with ownership and context so compliance teams can track mitigation and remediation work.', + }, +}; + +export const PUBLIC_OPERATION_METADATA: Record< + string, + PublicOperationMetadata +> = { + ...CORE_OPERATION_METADATA, + ...WORKFLOW_OPERATION_METADATA, + ...QUESTIONNAIRE_OPERATION_METADATA, +}; diff --git a/apps/api/src/openapi/public-docs-metadata.ts b/apps/api/src/openapi/public-docs-metadata.ts index a4f2023eb4..ed2c4312ee 100644 --- a/apps/api/src/openapi/public-docs-metadata.ts +++ b/apps/api/src/openapi/public-docs-metadata.ts @@ -1,198 +1,240 @@ import type { OpenAPIObject } from '@nestjs/swagger'; +import { PUBLIC_OPERATION_METADATA } from './operation-metadata'; +import { removeUnusedSchemas, sanitizePublicSchemas } from './schema-pruning'; +import { + toActionFragment, + toOperationDescription, + toSeoDescription, + toSeoTitle, +} from './seo-text'; +import { PUBLIC_TAG_METADATA } from './tag-metadata'; +import type { + OpenApiOperation, + PublicOperationMetadata, + PublicTagMetadata, + PublicVisibility, +} from './types'; export const PUBLIC_OPENAPI_TITLE = 'Comp AI API'; export const PUBLIC_OPENAPI_DESCRIPTION = - 'Comp AI API reference for automating compliance workflows, including evidence collection, policies, trust access, tasks, and security questionnaires.'; - -type PublicOperationMetadata = { - summary: string; - description: string; - href: string; - sidebarTitle?: string; - visibility?: 'public' | 'hidden' | 'excluded'; - content?: string; - codeSamples?: Array<{ - lang: string; - label?: string; - source: string; - }>; -}; - -type OpenApiOperation = { - operationId?: string; - summary?: string; - description?: string; - [key: string]: unknown; -}; - -const QUESTIONNAIRE_OPERATION_METADATA: Record< - string, - PublicOperationMetadata -> = { - QuestionnaireController_findAll_v1: { - summary: 'List questionnaires', - description: - 'List all security questionnaires for an organization with auth context for API clients and compliance automation workflows.', - href: '/api-reference/questionnaire/list-questionnaires', - }, - QuestionnaireController_findById_v1: { - summary: 'Get questionnaire details', - description: - 'Retrieve one saved security questionnaire, including parsed questions, answers, and authentication context for the requesting client.', - href: '/api-reference/questionnaire/get-a-questionnaire-by-id', - }, - QuestionnaireController_deleteById_v1: { - summary: 'Delete a questionnaire', - description: - 'Delete a saved security questionnaire for an organization when it is no longer needed for compliance review workflows.', - href: '/api-reference/questionnaire/delete-a-questionnaire', - }, - QuestionnaireController_parseQuestionnaire_v1: { - summary: 'Parse questionnaire content', - description: - 'Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.', - href: '/api-reference/questionnaire/parse-an-uploaded-questionnaire-file', - }, - QuestionnaireController_answerSingleQuestion_v1: { - summary: 'Answer one question', - description: - 'Generate an answer for one security questionnaire item using the organization evidence library and return source references.', - href: '/api-reference/questionnaire/answer-a-single-questionnaire-question', - }, - QuestionnaireController_saveAnswer_v1: { - summary: 'Save questionnaire answer', - description: - 'Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.', - href: '/api-reference/questionnaire/save-a-questionnaire-answer', - }, - QuestionnaireController_deleteAnswer_v1: { - summary: 'Delete questionnaire answer', - description: - 'Delete a stored questionnaire answer when it should be removed from the active response set.', - href: '/api-reference/questionnaire/delete-a-questionnaire-answer', - }, - QuestionnaireController_exportById_v1: { - summary: 'Export a questionnaire', - description: - 'Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor reviews.', - href: '/api-reference/questionnaire/export-a-questionnaire', - }, - QuestionnaireController_uploadAndParse_v1: { - summary: 'Start questionnaire parsing', - description: - 'Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.', - href: '/api-reference/questionnaire/upload-and-parse-a-questionnaire-file', - }, - QuestionnaireController_uploadAndParseUpload_v1: { - summary: 'Upload and parse file', - description: - 'Upload a questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.', - href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions', - }, - QuestionnaireController_parseQuestionnaireUpload_v1: { - summary: 'Auto-answer uploaded file', - description: - 'Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.', - href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export', - }, - QuestionnaireController_parseQuestionnaireUploadByToken_v1: { - summary: 'Auto-answer with Trust Access', - description: - 'Upload a questionnaire with a Trust Access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.', - href: '/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token', - sidebarTitle: 'Trust Access auto-answer', - content: - 'Use this endpoint when an external reviewer has an active Trust Access token and needs to upload a security questionnaire for automated answering. Comp AI validates the token, generates answers from approved Trust Center evidence, and returns a ZIP with completed PDF, CSV, and XLSX exports.', - codeSamples: [ - { - lang: 'bash', - label: 'Upload a questionnaire with a Trust Access token', - source: - 'curl --request POST --url "https://api.trycomp.ai/v1/questionnaire/parse/upload/token?token=$TRUST_ACCESS_TOKEN" --form "file=@security-questionnaire.xlsx"', - }, - ], - }, - QuestionnaireController_autoAnswerAndExport_v1: { - summary: 'Export generated answers', - description: - 'Generate and export questionnaire answers from a submitted payload using approved organization evidence.', - href: '/api-reference/questionnaire/export-questionnaire-answers', - }, - QuestionnaireController_autoAnswerAndExportUpload_v1: { - summary: 'Upload and export answers', - description: - 'Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.', - href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers', - }, - QuestionnaireController_autoAnswer_v1: { - summary: 'Stream generated answers', - description: - 'Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.', - href: '/api-reference/questionnaire/auto-answer-a-questionnaire', - }, -}; - -export const PUBLIC_OPERATION_METADATA: Record< - string, - PublicOperationMetadata -> = { - ...QUESTIONNAIRE_OPERATION_METADATA, -}; + 'Compliance automation API for SOC 2, ISO 27001, HIPAA, GDPR, evidence collection, policy workflows, Trust Access, security questionnaires, integrations, cloud checks, and device compliance.'; + +export const PUBLIC_SERVER_URL = 'https://api.trycomp.ai'; + +const EXCLUDED_PATH_PATTERNS = [ + /^\/v\d+\/admin(?:\/|$)/, + /^\/v\d+\/internal(?:\/|$)/, + /^\/v\d+\/auth(?:\/|$)/, + /^\/v\d+\/framework-editor(?:\/|$)/, + /^\/v\d+\/browserbase(?:\/|$)/, + /^\/v\d+\/assistant-chat(?:\/|$)/, + /^\/v\d+\/health(?:\/|$)/, + /^\/v\d+\/email\/unsubscribe(?:\/|$)/, + /^\/v\d+\/integrations\/webhooks(?:\/|$)/, + /^\/v\d+\/secrets(?:\/|$)/, + /^\/v\d+\/billing(?:\/|$)/, + /^\/v\d+\/background-check-billing(?:\/|$)/, + /^\/v\d+\/pentest-credits(?:\/|$)/, + /^\/v\d+\/finding-template(?:\/|$)/, + /^\/v\d+\/.*\/admin(?:\/|$)/, + /^\/v\d+\/.*\/webhooks?(?:\/|$)/, +]; + +function getVisibilityForOperation( + operation: OpenApiOperation, + metadata?: PublicOperationMetadata, +): PublicVisibility { + if (metadata?.visibility) { + return metadata.visibility; + } + + const tags = operation.tags ?? []; + if (tags.some((tag) => PUBLIC_TAG_METADATA[tag]?.visibility === 'excluded')) { + return 'excluded'; + } + + if (tags.some((tag) => PUBLIC_TAG_METADATA[tag]?.visibility === 'hidden')) { + return 'hidden'; + } + + return 'public'; +} + +function createMintMetadata( + metadata: PublicOperationMetadata, +): Record { + const sidebarTitle = metadata.sidebarTitle ?? metadata.summary; + const description = toSeoDescription(metadata.description); + const title = toSeoTitle(metadata.summary); + + return { + title, + sidebarTitle, + description, + 'og:title': title, + 'og:description': description, + }; +} + +function applyOperationMetadata( + operation: OpenApiOperation, + metadata: PublicOperationMetadata, +): void { + operation.summary = metadata.summary; + operation.description = metadata.description; + operation['x-mint'] = { + ...(metadata.href && { href: metadata.href }), + metadata: createMintMetadata(metadata), + ...(metadata.content && { content: metadata.content }), + }; + + if (metadata.codeSamples) { + operation['x-codeSamples'] = metadata.codeSamples; + } +} + +function createFallbackDescription(operation: OpenApiOperation): string { + const summary = operation.summary?.trim(); + const primaryTag = operation.tags?.[0]; + const tagDescription = primaryTag + ? PUBLIC_TAG_METADATA[primaryTag]?.description + : undefined; + + if (summary) { + const base = `${toActionFragment(summary)} in Comp AI.`; + + if (tagDescription) { + return toOperationDescription(`${base} ${tagDescription}`); + } + + return base; + } + + return tagDescription ?? PUBLIC_OPENAPI_DESCRIPTION; +} + +function applyFallbackOperationMetadata(operation: OpenApiOperation): void { + const summary = operation.summary?.trim(); + if (!summary) { + return; + } + + const fallbackDescription = createFallbackDescription(operation); + const existingDescription = operation.description?.trim(); + const description = + existingDescription && existingDescription.length >= 120 + ? existingDescription + : fallbackDescription; + + operation.description = toOperationDescription(description); + operation['x-mint'] = { + metadata: createMintMetadata({ + summary, + description, + }), + }; +} + +function applyVisibility( + operation: OpenApiOperation, + visibility: PublicVisibility, +): void { + if (visibility === 'hidden') { + operation['x-hidden'] = true; + delete operation['x-excluded']; + return; + } + + if (visibility === 'excluded') { + operation['x-excluded'] = true; + delete operation['x-hidden']; + return; + } + + delete operation['x-hidden']; + delete operation['x-excluded']; +} + +function addTagMetadata(document: OpenAPIObject): void { + const usedTags = new Set(); + for (const methods of Object.values(document.paths)) { + for (const operation of Object.values( + methods as Record, + )) { + for (const tag of operation.tags ?? []) { + usedTags.add(tag); + } + } + } + + document.tags = [...usedTags].sort().map((name) => { + const metadata: PublicTagMetadata | undefined = PUBLIC_TAG_METADATA[name]; + return { + name, + ...(metadata?.description && { description: metadata.description }), + ...(metadata?.group && { 'x-group': metadata.group }), + }; + }); +} + +function removeExcludedPaths(paths: Record): void { + for (const routePath of Object.keys(paths)) { + if (EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(routePath))) { + delete paths[routePath]; + } + } +} export function applyPublicOpenApiMetadata(document: OpenAPIObject): void { document.info.title = PUBLIC_OPENAPI_TITLE; document.info.description = PUBLIC_OPENAPI_DESCRIPTION; + document.servers = [ + { + url: PUBLIC_SERVER_URL, + description: 'Production API Server', + }, + ]; const paths = document.paths as Record< string, Record >; + removeExcludedPaths(paths); - for (const routePath of Object.keys(paths)) { - if (/^\/v\d+\/internal(?:\/|$)/.test(routePath)) { - delete paths[routePath]; - } - } - - for (const methods of Object.values(paths)) { - for (const operation of Object.values(methods)) { - if ( - !operation || - typeof operation !== 'object' || - !operation.operationId - ) { + for (const [routePath, methods] of Object.entries(paths)) { + for (const [method, operation] of Object.entries(methods)) { + if (!operation || typeof operation !== 'object') { continue; } - const metadata = PUBLIC_OPERATION_METADATA[operation.operationId]; - if (!metadata) { - continue; + const metadata = operation.operationId + ? PUBLIC_OPERATION_METADATA[operation.operationId] + : undefined; + + if (metadata) { + applyOperationMetadata(operation, metadata); } - operation.summary = metadata.summary; - operation.description = metadata.description; - operation['x-mint'] = { - href: metadata.href, - metadata: { - title: metadata.summary, - sidebarTitle: metadata.sidebarTitle ?? metadata.summary, - description: metadata.description, - 'og:title': metadata.summary, - 'og:description': metadata.description, - }, - ...(metadata.content && { content: metadata.content }), - }; - - if (metadata.visibility === 'hidden') { - operation['x-hidden'] = true; - } else if (metadata.visibility === 'excluded') { - operation['x-excluded'] = true; + const visibility = getVisibilityForOperation(operation, metadata); + if (visibility === 'excluded') { + delete methods[method]; + continue; } - if (metadata.codeSamples) { - operation['x-codeSamples'] = metadata.codeSamples; + if (!metadata) { + applyFallbackOperationMetadata(operation); } + + applyVisibility(operation, visibility); + } + + if (Object.keys(methods).length === 0) { + delete paths[routePath]; } } + + addTagMetadata(document); + removeUnusedSchemas(document); + sanitizePublicSchemas(document); } diff --git a/apps/api/src/openapi/questionnaire-metadata.ts b/apps/api/src/openapi/questionnaire-metadata.ts new file mode 100644 index 0000000000..9fe81f5e76 --- /dev/null +++ b/apps/api/src/openapi/questionnaire-metadata.ts @@ -0,0 +1,108 @@ +import type { PublicOperationMetadata } from './types'; + +export const QUESTIONNAIRE_OPERATION_METADATA: Record< + string, + PublicOperationMetadata +> = { + QuestionnaireController_findAll_v1: { + summary: 'List security questionnaires', + description: + 'List saved security questionnaires for an organization so teams can track customer reviews, answer status, and response history.', + href: '/api-reference/questionnaire/list-questionnaires', + }, + QuestionnaireController_findById_v1: { + summary: 'Get security questionnaire details', + description: + 'Retrieve one saved security questionnaire, including extracted questions, generated answers, and review context for the requesting client.', + href: '/api-reference/questionnaire/get-a-questionnaire-by-id', + }, + QuestionnaireController_deleteById_v1: { + summary: 'Delete a security questionnaire', + description: + 'Delete a saved security questionnaire when a customer review or vendor assessment no longer needs to be retained.', + href: '/api-reference/questionnaire/delete-a-questionnaire', + }, + QuestionnaireController_parseQuestionnaire_v1: { + summary: 'Parse questionnaire content', + description: + 'Parse questionnaire content from a submitted payload so teams can extract security questions before generating or reviewing answers.', + href: '/api-reference/questionnaire/parse-an-uploaded-questionnaire-file', + }, + QuestionnaireController_answerSingleQuestion_v1: { + summary: 'Answer one questionnaire question', + description: + 'Generate an answer for one security questionnaire item using the organization evidence library and return source references for review.', + href: '/api-reference/questionnaire/answer-a-single-questionnaire-question', + }, + QuestionnaireController_saveAnswer_v1: { + summary: 'Save questionnaire answer', + description: + 'Save a manual or AI-generated security questionnaire answer for later review, export, and audit tracking.', + href: '/api-reference/questionnaire/save-a-questionnaire-answer', + }, + QuestionnaireController_deleteAnswer_v1: { + summary: 'Delete questionnaire answer', + description: + 'Delete a stored questionnaire answer when it should be removed from the active response set.', + href: '/api-reference/questionnaire/delete-a-questionnaire-answer', + }, + QuestionnaireController_exportById_v1: { + summary: 'Export a security questionnaire', + description: + 'Export a saved security questionnaire response package as PDF, CSV, or XLSX for customer and vendor security reviews.', + href: '/api-reference/questionnaire/export-a-questionnaire', + }, + QuestionnaireController_uploadAndParse_v1: { + summary: 'Start questionnaire parsing', + description: + 'Upload a questionnaire payload and start asynchronous parsing, returning a run ID for real-time progress tracking.', + href: '/api-reference/questionnaire/upload-and-parse-a-questionnaire-file', + }, + QuestionnaireController_uploadAndParseUpload_v1: { + summary: 'Upload and parse questionnaire file', + description: + 'Upload a security questionnaire file, extract questions, save the parsed questionnaire, and return its identifier and question count.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-parse-its-questions', + }, + QuestionnaireController_parseQuestionnaireUpload_v1: { + summary: 'Auto-answer uploaded questionnaire', + description: + 'Upload a questionnaire file and generate answer exports from approved organization evidence in PDF, CSV, or XLSX format.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export', + }, + QuestionnaireController_parseQuestionnaireUploadByToken_v1: { + summary: 'Upload questionnaire with Trust Access', + description: + 'Upload a questionnaire with a Trust Portal access token and return a ZIP containing answered PDF, CSV, and XLSX exports for reviewers.', + href: '/api-reference/questionnaire/upload-and-auto-answer-a-questionnaire-via-trust-portal-token', + sidebarTitle: 'Trust Access auto-answer', + content: + 'Use this endpoint when an external reviewer has an active Trust Access token and needs to upload a security questionnaire for automated answering. Comp AI validates the token, generates answers from approved Trust Center evidence, and returns a ZIP with completed PDF, CSV, and XLSX exports.', + codeSamples: [ + { + lang: 'bash', + label: 'Upload a questionnaire with a Trust Access token', + source: + 'curl --request POST --url "https://api.trycomp.ai/v1/questionnaire/parse/upload/token?token=$TRUST_ACCESS_TOKEN" --form "file=@security-questionnaire.xlsx"', + }, + ], + }, + QuestionnaireController_autoAnswerAndExport_v1: { + summary: 'Export generated questionnaire answers', + description: + 'Generate and export questionnaire answers from a submitted payload using approved organization evidence.', + href: '/api-reference/questionnaire/export-questionnaire-answers', + }, + QuestionnaireController_autoAnswerAndExportUpload_v1: { + summary: 'Upload and export generated answers', + description: + 'Upload a questionnaire file and return generated answer exports in PDF, CSV, or XLSX format.', + href: '/api-reference/questionnaire/upload-a-questionnaire-file-and-export-auto-generated-answers', + }, + QuestionnaireController_autoAnswer_v1: { + summary: 'Stream generated questionnaire answers', + description: + 'Stream generated questionnaire answers over server-sent events so clients can show progress while answers are produced.', + href: '/api-reference/questionnaire/auto-answer-a-questionnaire', + }, +}; diff --git a/apps/api/src/openapi/schema-pruning.ts b/apps/api/src/openapi/schema-pruning.ts new file mode 100644 index 0000000000..71d621d184 --- /dev/null +++ b/apps/api/src/openapi/schema-pruning.ts @@ -0,0 +1,81 @@ +import type { OpenAPIObject } from '@nestjs/swagger'; + +function collectSchemaRefs(value: unknown, refs: Set): void { + if (Array.isArray(value)) { + for (const item of value) collectSchemaRefs(item, refs); + return; + } + + if (!value || typeof value !== 'object') { + return; + } + + for (const [key, child] of Object.entries(value)) { + if ( + key === '$ref' && + typeof child === 'string' && + child.startsWith('#/components/schemas/') + ) { + refs.add(child.replace('#/components/schemas/', '')); + continue; + } + + collectSchemaRefs(child, refs); + } +} + +export function removeUnusedSchemas(document: OpenAPIObject): void { + const schemas = document.components?.schemas; + if (!schemas) { + return; + } + + const referencedSchemas = new Set(); + collectSchemaRefs(document.paths, referencedSchemas); + + let size = 0; + while (size !== referencedSchemas.size) { + size = referencedSchemas.size; + for (const schemaName of [...referencedSchemas]) { + collectSchemaRefs(schemas[schemaName], referencedSchemas); + } + } + + for (const schemaName of Object.keys(schemas)) { + if (!referencedSchemas.has(schemaName)) { + delete schemas[schemaName]; + } + } +} + +function sanitizeSchemaDetails(value: unknown): void { + if (Array.isArray(value)) { + for (const item of value) sanitizeSchemaDetails(item); + return; + } + + if (!value || typeof value !== 'object') { + return; + } + + const record = value as Record; + if ( + Array.isArray(record.enum) && + record.enum.includes('secrets_info_disclosure') + ) { + delete record.enum; + } + + for (const child of Object.values(record)) { + sanitizeSchemaDetails(child); + } +} + +export function sanitizePublicSchemas(document: OpenAPIObject): void { + const schemas = document.components?.schemas; + if (!schemas) { + return; + } + + sanitizeSchemaDetails(schemas); +} diff --git a/apps/api/src/openapi/seo-text.ts b/apps/api/src/openapi/seo-text.ts new file mode 100644 index 0000000000..7ae083185e --- /dev/null +++ b/apps/api/src/openapi/seo-text.ts @@ -0,0 +1,53 @@ +function toSentence(value: string): string { + const normalized = value.replace(/\s+/g, ' ').trim(); + if (!normalized) { + return normalized; + } + + return /[.!?]$/.test(normalized) ? normalized : `${normalized}.`; +} + +function trimToWordBoundary(value: string, maxLength: number): string { + if (value.length <= maxLength) { + return value; + } + + const truncated = value.slice(0, maxLength - 1); + const boundary = truncated.lastIndexOf(' '); + const safe = boundary > 80 ? truncated.slice(0, boundary) : truncated; + return `${safe.replace(/[.,;:!?]+$/, '')}.`; +} + +function trimTitle(value: string, maxLength: number): string { + const normalized = value.replace(/\s+/g, ' ').trim(); + if (normalized.length <= maxLength) { + return normalized; + } + + const truncated = normalized.slice(0, maxLength); + const boundary = truncated.lastIndexOf(' '); + return (boundary > 32 ? truncated.slice(0, boundary) : truncated).replace( + /[.,;:!?]+$/, + '', + ); +} + +export function toSeoTitle(summary: string): string { + const suffix = ' | Comp AI API'; + return `${trimTitle(summary, 60 - suffix.length)}${suffix}`; +} + +export function toSeoDescription(value: string): string { + return trimToWordBoundary(toSentence(value), 158); +} + +export function toOperationDescription(value: string): string { + return trimToWordBoundary(toSentence(value), 240); +} + +export function toActionFragment(value: string): string { + return value + .replace(/\s+/g, ' ') + .trim() + .replace(/[.!?]+$/, ''); +} diff --git a/apps/api/src/openapi/tag-metadata.ts b/apps/api/src/openapi/tag-metadata.ts new file mode 100644 index 0000000000..563ba779a7 --- /dev/null +++ b/apps/api/src/openapi/tag-metadata.ts @@ -0,0 +1,190 @@ +import type { PublicTagMetadata } from './types'; + +export const PUBLIC_TAG_METADATA: Record = { + 'Assistant Chat': { + description: + 'Internal AI assistant endpoints for product chat history and streamed completions.', + visibility: 'excluded', + }, + Attachments: { + description: + 'Generate signed download links for files attached to compliance tasks, comments, evidence records, and workflow reviews.', + }, + 'Audit Logs': { + description: + 'Retrieve audit trails for compliance activity, evidence changes, access decisions, and customer-facing security review workflows.', + }, + 'Background Check Billing': { + description: + 'Manage Stripe setup and billing sessions for background check purchases inside the Comp AI app.', + visibility: 'excluded', + }, + 'Background Checks': { + description: + 'Request, review, and attach employee background checks used as people-security evidence for compliance programs.', + }, + Billing: { + description: + 'Manage organization billing status, preferences, checkout sessions, customer portal links, and Stripe webhook handling.', + visibility: 'excluded', + }, + Browserbase: { + description: + 'Internal browser automation endpoints used to collect auditable evidence from web applications.', + visibility: 'excluded', + }, + CloudSecurity: { + group: 'Cloud Security', + description: + 'Run AWS, Azure, and GCP cloud security scans, detect enabled services, review findings, and connect cloud posture results to compliance work.', + }, + Comments: { + description: + 'Create and manage collaboration comments on compliance entities such as tasks, policies, risks, vendors, and findings.', + }, + Context: { + description: + 'Manage organization context that helps Comp AI tailor policies, assessments, and compliance automation to the business.', + }, + Controls: { + description: + 'Manage controls, map them to policies, tasks, framework requirements, and evidence document types, and track implementation progress.', + }, + 'Device Agent': { + description: + 'Register employee devices, submit device compliance check-ins, download agent builds, and manage endpoint security status.', + }, + Devices: { + description: + 'Read and manage employee device inventory and Fleet compliance data used for endpoint security controls.', + }, + 'Email - Unsubscribe': { + description: + 'Handle one-click email unsubscribe requests for notification compliance.', + visibility: 'excluded', + }, + 'Evidence Export': { + description: + 'Export task evidence, automation evidence, and reviewer-ready evidence bundles as PDF or ZIP files.', + }, + 'Evidence Export (Auditor)': { + description: + 'Export all organization evidence for an auditor review package.', + }, + 'Evidence Forms': { + description: + 'Collect, review, upload, and export structured evidence submissions for compliance tasks and document requirements.', + }, + 'Finding Templates': { + description: + 'Manage reusable finding templates used by platform administrators and audit workflows.', + visibility: 'excluded', + }, + Findings: { + description: + 'Create, review, update, and track audit findings, remediation activity, and finding history for an organization.', + }, + Frameworks: { + description: + 'Manage SOC 2, ISO 27001, HIPAA, GDPR, FedRAMP, and custom framework instances, requirements, scores, and sync history.', + }, + Health: { + description: 'Check API service health for uptime monitoring.', + visibility: 'excluded', + }, + Integrations: { + description: + 'Connect vendor systems, configure OAuth apps, run compliance checks, sync employees, manage variables, and collect automated evidence.', + }, + 'Knowledge Base': { + description: + 'Upload source documents, process them for retrieval, and manage reusable manual answers that power questionnaires and AI policy workflows.', + }, + 'Org Chart': { + description: + 'Manage organization chart metadata and evidence used for governance, accountability, and audit readiness.', + }, + Organization: { + description: + 'Manage organization profile data, API keys, logos, ownership, role notifications, and access approval settings.', + }, + People: { + description: + 'Invite and manage workforce members, training status, device compliance, email preferences, and employee evidence records.', + }, + 'Pentest Credits': { + description: + 'Read penetration-test credit balances used by the Comp AI purchasing flow.', + visibility: 'excluded', + }, + Policies: { + description: + 'Create, version, publish, export, map, and improve compliance policies with AI-assisted drafting and approval workflows.', + }, + Questionnaire: { + description: + 'Parse security questionnaires, generate answers from approved evidence, save reviewer edits, stream progress, and export completed files.', + }, + Remediation: { + description: + 'Preview, execute, roll back, and track cloud security remediation actions for supported AWS, Azure, and GCP findings.', + }, + Risks: { + description: + 'Create, update, and report on organizational risks with ownership, departments, and compliance remediation status.', + }, + Roles: { + description: + 'Create custom roles and resolve permission sets for organization-level access control.', + }, + SOA: { + group: 'Statement of Applicability', + description: + 'Create, auto-fill, review, approve, and export ISO 27001 Statement of Applicability documents.', + }, + Secrets: { + description: + 'Store and manage encrypted automation secrets used by evidence collection and integration workflows.', + visibility: 'excluded', + }, + 'Security Penetration Tests': { + description: + 'Create AI-powered penetration test runs, track progress, inspect findings and events, and download markdown or PDF reports.', + }, + 'Task Automations': { + description: + 'Create, version, run, and inspect automated evidence collection workflows attached to compliance tasks.', + }, + 'Task Management': { + description: + 'Manage task items and attachments linked to operational entities such as risks and vendors.', + }, + Tasks: { + description: + 'Manage compliance task lifecycle, assignments, review approvals, evidence uploads, policy links, and activity history.', + }, + Timelines: { + description: + 'Track audit and compliance readiness timelines, phases, and review milestones for an organization.', + }, + Training: { + description: + 'Record security awareness and HIPAA training completion status and generate completion certificates.', + }, + 'Trust Access': { + description: + 'Manage external Trust Center access requests, NDA signing, grants, tokenized document downloads, public FAQs, and reviewer access.', + }, + 'Trust Portal': { + description: + 'Configure the live Trust Center, custom domain, public overview, FAQs, compliance resources, documents, links, and vendor disclosures.', + }, + Vendors: { + description: + 'Manage third-party vendors, global vendor search, risk assessment triggers, and Trust Center vendor visibility.', + }, + Webhook: { + description: 'Receive provider webhook events for integration workflows.', + visibility: 'excluded', + }, +}; diff --git a/apps/api/src/openapi/types.ts b/apps/api/src/openapi/types.ts new file mode 100644 index 0000000000..7daedf3182 --- /dev/null +++ b/apps/api/src/openapi/types.ts @@ -0,0 +1,29 @@ +export type PublicVisibility = 'public' | 'hidden' | 'excluded'; + +export type PublicOperationMetadata = { + summary: string; + description: string; + href?: string; + sidebarTitle?: string; + visibility?: PublicVisibility; + content?: string; + codeSamples?: Array<{ + lang: string; + label?: string; + source: string; + }>; +}; + +export type PublicTagMetadata = { + description: string; + group?: string; + visibility?: PublicVisibility; +}; + +export type OpenApiOperation = { + operationId?: string; + tags?: string[]; + summary?: string; + description?: string; + [key: string]: unknown; +}; diff --git a/apps/api/src/openapi/workflow-operation-metadata.ts b/apps/api/src/openapi/workflow-operation-metadata.ts new file mode 100644 index 0000000000..1d7e90399d --- /dev/null +++ b/apps/api/src/openapi/workflow-operation-metadata.ts @@ -0,0 +1,147 @@ +import type { PublicOperationMetadata } from './types'; + +export const WORKFLOW_OPERATION_METADATA: Record< + string, + PublicOperationMetadata +> = { + FrameworksController_findAll_v1: { + summary: 'List compliance frameworks', + description: + 'List active SOC 2, ISO 27001, HIPAA, GDPR, FedRAMP, and custom compliance frameworks with implementation status and progress data.', + }, + FrameworksController_addFrameworks_v1: { + summary: 'Add compliance frameworks', + description: + 'Add one or more compliance frameworks to an organization so tasks, controls, evidence, and readiness tracking can be generated.', + }, + FrameworksController_findAvailable_v1: { + summary: 'List available frameworks', + description: + 'List frameworks available for activation before starting a new compliance program or expanding into another standard.', + }, + FrameworksController_getScores_v1: { + summary: 'Get framework readiness scores', + description: + 'Retrieve framework readiness scores so teams can report progress toward audit readiness across active compliance standards.', + }, + FrameworksController_syncFramework_v1: { + summary: 'Sync framework requirements', + description: + 'Sync framework requirements, controls, and tasks after framework content changes so compliance tracking remains current.', + }, + ControlsController_findAll_v1: { + summary: 'List compliance controls', + description: + 'List controls with linked policies, tasks, requirements, and document types for SOC 2, ISO 27001, HIPAA, and GDPR programs.', + }, + ControlsController_create_v1: { + summary: 'Create compliance control', + description: + 'Create a custom compliance control and connect it to framework requirements, policies, tasks, and evidence expectations.', + }, + ControlsController_linkPolicies_v1: { + summary: 'Link policies to control', + description: + 'Link policies to a control so auditors and reviewers can trace control implementation back to approved policy evidence.', + }, + ControlsController_linkTasks_v1: { + summary: 'Link tasks to control', + description: + 'Link compliance tasks to a control so implementation work, evidence collection, and review status stay connected.', + }, + EvidenceFormsController_listForms_v1: { + summary: 'List evidence forms', + description: + 'List structured evidence forms that collect recurring submissions for security, HR, IT, finance, and compliance workflows.', + }, + EvidenceFormsController_submitForm_v1: { + summary: 'Submit evidence form', + description: + 'Submit structured evidence responses and attachments for review against a compliance task or document requirement.', + }, + EvidenceFormsController_reviewSubmission_v1: { + summary: 'Review evidence submission', + description: + 'Approve or reject a submitted evidence form so task status and audit readiness reflect the latest review decision.', + }, + EvidenceFormsController_exportCsv_v1: { + summary: 'Export evidence submissions', + description: + 'Export evidence form submissions as CSV for auditor requests, offline review, or internal compliance reporting.', + }, + PeopleController_getAllPeople_v1: { + summary: 'List workforce members', + description: + 'List employees and contractors with onboarding, training, device, and compliance status used for people-security controls.', + }, + PeopleController_inviteMembers_v1: { + summary: 'Invite workforce members', + description: + 'Invite employees or contractors to complete portal tasks, training, device setup, and compliance evidence requirements.', + }, + PeopleController_getFleetCompliance_v1: { + summary: 'Get fleet compliance', + description: + 'Retrieve Fleet device compliance status so endpoint security findings can support people-security controls and audit evidence.', + }, + TrainingController_getCompletions_v1: { + summary: 'List training completions', + description: + 'List security awareness and HIPAA training completion records for workforce compliance tracking and audit evidence.', + }, + TrainingController_generateCertificate_v1: { + summary: 'Generate training certificate', + description: + 'Generate a training completion certificate that can be shared with auditors or attached as workforce security evidence.', + }, + VendorsController_getAllVendors_v1: { + summary: 'List vendors', + description: + 'List third-party vendors with risk level, owner, assessment status, and Trust Center visibility for vendor risk management.', + }, + VendorsController_createVendor_v1: { + summary: 'Create vendor', + description: + 'Create a vendor record so teams can track third-party risk, assessment evidence, owner, category, and compliance status.', + }, + VendorsController_searchGlobalVendors_v1: { + summary: 'Search global vendors', + description: + 'Search global vendor records to prefill vendor profiles and speed up third-party risk assessment workflows.', + }, + RisksController_getAllRisks_v1: { + summary: 'List organization risks', + description: + 'List organization risks with owners, departments, severity, mitigation status, and evidence for risk management reporting.', + }, + RisksController_updateRisk_v1: { + summary: 'Update organization risk', + description: + 'Update a risk record as mitigation work progresses so compliance reports reflect the current risk posture.', + }, + FindingsController_listFindings_v1: { + summary: 'List audit findings', + description: + 'List audit findings with status, severity, owner, history, and remediation context for compliance review workflows.', + }, + FindingsController_createFinding_v1: { + summary: 'Create audit finding', + description: + 'Create an audit finding so teams can track issue ownership, remediation activity, severity, and supporting evidence.', + }, + AuditLogController_getAuditLogs_v1: { + summary: 'List audit logs', + description: + 'List organization audit logs for compliance activity, access changes, evidence updates, and customer-facing review events.', + }, + SOAController_autoFill_v1: { + summary: 'Auto-fill ISO 27001 SOA', + description: + 'Auto-fill a Statement of Applicability draft using organization context and framework mappings for ISO 27001 review.', + }, + SOAController_exportDocument_v1: { + summary: 'Export ISO 27001 SOA', + description: + 'Export the approved Statement of Applicability document for ISO 27001 auditors, customer reviews, and internal records.', + }, +}; diff --git a/packages/docs/api-reference/overview.mdx b/packages/docs/api-reference/overview.mdx index 603f5a23e7..bb8bd1210d 100644 --- a/packages/docs/api-reference/overview.mdx +++ b/packages/docs/api-reference/overview.mdx @@ -1,50 +1,96 @@ --- -title: 'Comp AI API' -description: 'Automate compliance workflows with the Comp AI API, including evidence collection, policies, Trust Access, tasks, and security questionnaires.' +title: 'Compliance Automation API' +description: 'Use the Comp AI API to automate SOC 2 evidence, policies, Trust Access, security questionnaires, tasks, and compliance workflows.' --- -The Comp AI API helps teams connect compliance workflows to their own systems. Use it to build internal tools, automate evidence collection, manage questionnaires, and connect Trust Center access flows to the rest of your security program. +The Comp AI API lets engineering, security, and compliance teams connect their internal systems to the compliance workflows they run in Comp AI. + +Use it to automate evidence collection, manage policies, coordinate compliance tasks, answer security questionnaires, configure Trust Center access, track risks and vendors, and keep SOC 2, ISO 27001, HIPAA, GDPR, and FedRAMP programs connected to the systems where work actually happens. ## Authentication -Comp AI API requests use the `X-API-Key` header. Create and manage API keys inside your Comp AI organization, then keep keys server-side and scoped to the workflow you are automating. +Most organization automation uses the `X-API-Key` header. Create scoped API keys inside your Comp AI organization, keep them server-side, and grant only the permissions needed for the workflow you are building. ```bash -curl https://api.trycomp.ai/v1/organization \ - -H "X-API-Key: $COMP_AI_API_KEY" +curl --request GET \ + --url "https://api.trycomp.ai/v1/organization" \ + --header "X-API-Key: $COMP_AI_API_KEY" ``` -## Common API Workflows +Some reference pages document other authentication flows because they are part of the product surface: + +- Trust Access endpoints can use tokenized reviewer links for customer security reviews. +- Device Agent endpoints are used by signed-in employee devices. +- Webhook endpoints are intentionally omitted from the public reference unless they are useful for implementers. +- Internal, admin-only, health, assistant, and browser automation routes are excluded from Mintlify docs. + +## API Surface + +| Area | What you can automate | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| Evidence and tasks | List compliance tasks, upload evidence, export audit-ready evidence bundles, and create recurring evidence automations. | +| Policies and knowledge base | Create, version, publish, export, and improve policies while keeping approved source material available for AI-assisted workflows. | +| Security questionnaires | Upload questionnaires, extract questions, generate answers from approved evidence, review edits, and export completed files. | +| Trust Center and Trust Access | Manage access requests, NDA signing, grants, document downloads, certificates, FAQs, custom links, and public overview content. | +| Frameworks and controls | Track framework readiness, map controls to policies, tasks, requirements, and evidence expectations. | +| Integrations and cloud checks | Connect providers, run compliance checks, trigger AWS, Azure, and GCP scans, and review cloud security findings. | +| People, devices, and training | Track workforce members, device compliance, training completions, and endpoint evidence for people-security controls. | +| Vendors, risks, and findings | Manage third-party risk, organizational risks, audit findings, remediation status, and supporting evidence. | +| Penetration tests | Create security assessment runs, track progress, inspect findings, and download report artifacts. | + +## Common Workflows - - Upload questionnaires, extract security questions, generate answers from approved evidence, and export reviewer-ready files. + + Connect tasks to recurring evidence collection so audit proof stays current. - Read organization details and build internal admin workflows around Comp AI account data. + Upload questionnaires, generate approved answers, and export reviewer-ready files. + + + + Draft, version, publish, and export policies that support audits and customer reviews. - Connect API workflows to Trust Center access requests, reviewer tokens, and shared compliance - resources. + Approve external reviewers, collect NDAs, and share controlled Trust Center resources. - - Understand how Comp AI extracts questions and generates answers from published policies and knowledge base content. + + Connect SaaS, cloud, and security tools that provide continuous compliance evidence. + + + + Run AWS, Azure, and GCP security checks and turn findings into remediation work. + + + + Register endpoints and submit device security check-ins for workforce controls. + + + + Start security assessment runs and download report deliverables for review. -## Security Questionnaire Automation +## Generated Reference Quality + +The endpoint reference is generated from the NestJS OpenAPI specification used by the Comp AI API. Endpoint titles, descriptions, examples, visibility rules, and Mintlify metadata are maintained in the API source so generated pages stay aligned with product behavior as routes change. + +Public customer-facing endpoints are documented. Internal operations, admin tooling, health checks, webhooks that are not useful for implementers, and routes that would expose private implementation details are intentionally excluded or hidden. -The highest-leverage API workflow is security questionnaire automation. Comp AI can parse uploaded questionnaires, generate answers from approved organization evidence, and export completed files in common review formats. +## Implementation Guidance -For external reviewers who use Trust Access tokens, use the token-based upload endpoint. For authenticated organization workflows, use the standard questionnaire endpoints with an API key. +Start with the workflow you want to automate, then create a scoped API key for that workflow. For most teams, the first high-value integrations are: -## Public API Reference +1. Syncing evidence and task status into internal compliance reporting. +2. Uploading source documents to improve questionnaire and policy workflows. +3. Connecting Trust Center access approvals to CRM or customer-security processes. +4. Exporting evidence, policies, or questionnaire results for auditor and customer reviews. -The API reference is generated from the same NestJS OpenAPI specification used by the Comp AI API. Endpoint titles, descriptions, and visibility are curated in the API source so generated Mintlify pages stay accurate when the API changes. +Use production API calls against `https://api.trycomp.ai` and avoid storing API keys in client-side code. diff --git a/packages/docs/automated-evidence.mdx b/packages/docs/automated-evidence.mdx index dc3edb68ae..1d2a25539d 100644 --- a/packages/docs/automated-evidence.mdx +++ b/packages/docs/automated-evidence.mdx @@ -1,11 +1,20 @@ --- -title: "Automated Evidence" -description: "Comp AI Automated Evidence allows you to streamline evidence collection for recurring compliance tasks. " +title: 'Automated Evidence' +description: 'Automate recurring SOC 2, ISO 27001, HIPAA, and GDPR evidence collection from connected systems with Comp AI.' --- +Automated Evidence keeps compliance tasks current by collecting recurring proof from the systems your team already uses. Use it to reduce stale screenshots, manual follow-ups, and last-minute audit evidence work. + ### Automated Evidence Demo -