From 85582656bd1dff5ab25f700ffefbc9994434cb68 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Sun, 10 May 2026 01:49:30 -0400 Subject: [PATCH 1/5] feat(cloud-security): add AWS partition handling and GovCloud support - Introduced functions to infer AWS partition and region based on findings. - Updated remediation prompts to include AWS execution context. - Enhanced ARN normalization for AWS and GovCloud. - Added validation for AWS partition configurations. - Implemented tests for AWS partition utilities. - Updated various services and controllers to support AWS partitioning and GovCloud integration. --- apps/api/.env.example | 5 + .../cloud-security/ai-remediation.prompt.ts | 46 ++++- .../cloud-security/aws-command-executor.ts | 50 +++++ .../aws-partition.utils.spec.ts | 73 ++++++++ .../src/cloud-security/aws-partition.utils.ts | 100 ++++++++++ .../cloud-security-query.service.ts | 5 + .../providers/aws-security.service.ts | 112 ++++++++++- .../src/cloud-security/remediation.service.ts | 6 +- .../controllers/connections.controller.ts | 70 ++++--- .../components/CloudTestsSection.tsx | 21 ++- .../cloud-tests/components/ProviderTabs.tsx | 1 + .../app/(app)/[orgId]/cloud-tests/types.ts | 1 + .../components/EmptyStateOnboarding.test.tsx | 5 + .../components/EmptyStateOnboarding.tsx | 176 +++++++++++++----- .../components/aws-account-settings-body.tsx | 112 ++++++++++- .../[slug]/components/connection-display.ts | 2 +- .../integrations/CloudShellSetup.tsx | 20 +- .../integrations/ConnectIntegrationDialog.tsx | 67 ++++++- .../integrations/CredentialInput.tsx | 21 ++- packages/docs/openapi.json | 35 ++++ packages/integration-platform/src/index.ts | 9 +- .../src/manifests/aws/credentials.ts | 97 ++++++++-- 22 files changed, 909 insertions(+), 125 deletions(-) create mode 100644 apps/api/src/cloud-security/aws-partition.utils.spec.ts create mode 100644 apps/api/src/cloud-security/aws-partition.utils.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index b153ce2c1b..fedfeaf98a 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -53,4 +53,9 @@ BACKGROUND_CHECK_WEBHOOK_SECRET= BACKGROUND_WH_ENDPOINT= STRIPE_BACKGROUND_CHECK_PRICE_ID=price_1TRWckCkFWhKYvHIA1GLv1sO +# We are not using security hub service it is only the variable prefix for matching with old one SECURITY_HUB_ROLE_ASSUMER_ARN= +SECURITY_HUB_GOVCLOUD_ROLE_ASSUMER_ARN= +SECURITY_HUB_GOVCLOUD_ACCESS_KEY_ID= +SECURITY_HUB_GOVCLOUD_SECRET_ACCESS_KEY= +SECURITY_HUB_GOVCLOUD_SESSION_TOKEN= diff --git a/apps/api/src/cloud-security/ai-remediation.prompt.ts b/apps/api/src/cloud-security/ai-remediation.prompt.ts index d175f39317..7885e529bf 100644 --- a/apps/api/src/cloud-security/ai-remediation.prompt.ts +++ b/apps/api/src/cloud-security/ai-remediation.prompt.ts @@ -99,6 +99,27 @@ export type CompletePermissions = z.infer; // ─── Prompt Builders ──────────────────────────────────────────────────────── +function inferAwsPartition(finding: { + resourceId: string; + evidence: Record; +}): 'aws' | 'aws-us-gov' { + if (finding.resourceId.startsWith('arn:aws-us-gov:')) return 'aws-us-gov'; + + const region = + typeof finding.evidence.region === 'string' ? finding.evidence.region : ''; + return region.startsWith('us-gov-') ? 'aws-us-gov' : 'aws'; +} + +function inferAwsRegion(finding: { + resourceId: string; + evidence: Record; +}): string { + if (typeof finding.evidence.region === 'string') return finding.evidence.region; + + const arnMatch = finding.resourceId.match(/^arn:[^:]+:[^:]*:([^:]*):/); + return arnMatch?.[1] || 'the execution region'; +} + const SYSTEM_PROMPT = `You are an AWS security remediation expert. You analyze security findings and produce structured fix plans that will be executed by an automated system using AWS SDK v3. A human will ALWAYS review your plan before execution. Be precise and correct. @@ -117,12 +138,20 @@ A human will ALWAYS review your plan before execution. Be precise and correct. ## RESOURCE ID PARSING - Extract actual resource names from ARNs: - - "arn:aws:s3:::my-bucket" → Bucket: "my-bucket" - - "arn:aws:kms:us-east-1:123:key/abc" → KeyId: "arn:aws:kms:us-east-1:123:key/abc" (use full ARN for KMS) - - "arn:aws:rds:us-east-1:123:db:mydb" → DBInstanceIdentifier: "mydb" - - "arn:aws:ec2:us-east-1:123:vpc/vpc-abc" → VpcId: "vpc-abc" + - "arn:aws:s3:::my-bucket" or "arn:aws-us-gov:s3:::my-bucket" → Bucket: "my-bucket" + - "arn:aws:kms:us-east-1:123:key/abc" → KeyId: use the full ARN exactly as provided + - "arn:aws-us-gov:kms:us-gov-west-1:123:key/abc" → KeyId: use the full GovCloud ARN exactly as provided + - "arn:aws:rds:us-east-1:123:db:mydb" or "arn:aws-us-gov:rds:us-gov-west-1:123:db:mydb" → DBInstanceIdentifier: "mydb" + - "arn:aws:ec2:us-east-1:123:vpc/vpc-abc" or "arn:aws-us-gov:ec2:us-gov-west-1:123:vpc/vpc-abc" → VpcId: "vpc-abc" - Use the correct parameter names that the AWS SDK expects +## AWS PARTITIONS AND GOVCLOUD +- Preserve the AWS partition from the finding context. +- If AWS Partition is "aws-us-gov", every ARN you create or pass MUST start with "arn:aws-us-gov:". +- If AWS Partition is "aws", every ARN you create or pass MUST start with "arn:aws:". +- Never convert a GovCloud ARN to a commercial AWS ARN. +- For GovCloud findings, use GovCloud regions such as "us-gov-west-1" or "us-gov-east-1"; never default to "us-east-1". + ## SAFETY RULES (NEVER violate) - NEVER delete data, buckets, tables, databases, or file systems - NEVER modify IAM policies, roles, or users in ways that could lock out users @@ -259,10 +288,19 @@ export function buildFixPlanPrompt(finding: { findingKey: string; evidence: Record; }): string { + const awsPartition = inferAwsPartition(finding); + const awsRegion = inferAwsRegion(finding); + return `Analyze this AWS security finding and generate a fix plan. IMPORTANT: Your fix must change the EXACT AWS setting/resource that caused this finding. The scan will re-check the same thing after the fix — if you fix something different, the finding will persist. +AWS EXECUTION CONTEXT: +- AWS Partition: ${awsPartition} +- Region: ${awsRegion} +- When constructing ARNs, use partition prefix: arn:${awsPartition}: +- If region-specific values are needed, use this region unless the finding explicitly gives a different one. + FINDING: - Title: ${finding.title} - Description: ${finding.description ?? 'N/A'} diff --git a/apps/api/src/cloud-security/aws-command-executor.ts b/apps/api/src/cloud-security/aws-command-executor.ts index 2fc2837077..8964cf58df 100644 --- a/apps/api/src/cloud-security/aws-command-executor.ts +++ b/apps/api/src/cloud-security/aws-command-executor.ts @@ -1,5 +1,9 @@ import type { AwsCredentialIdentity } from '@aws-sdk/types'; import type { AwsCommandStep } from './ai-remediation.prompt'; +import { + type AwsPartition, + getAwsPartitionForRegion, +} from './aws-partition.utils'; import * as s3 from '@aws-sdk/client-s3'; import * as dynamodb from '@aws-sdk/client-dynamodb'; @@ -145,6 +149,50 @@ const JSON_STRING_PARAMS = new Set([ 'Definition', ]); +function normalizeArnPartition(value: string, partition: AwsPartition): string { + if (partition === 'aws-us-gov') { + return value.replace(/\barn:aws:/g, 'arn:aws-us-gov:'); + } + + return value; +} + +function normalizeArnPartitionsInValue( + value: unknown, + partition: AwsPartition, +): unknown { + if (typeof value === 'string') { + return normalizeArnPartition(value, partition); + } + + if (Array.isArray(value)) { + return value.map((item) => normalizeArnPartitionsInValue(item, partition)); + } + + if (value !== null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [ + key, + normalizeArnPartitionsInValue(item, partition), + ]), + ); + } + + return value; +} + +function normalizeArnPartitions( + input: Record, + region: string, +): void { + const partition = getAwsPartitionForRegion(region); + if (partition === 'aws') return; + + for (const [key, value] of Object.entries(input)) { + input[key] = normalizeArnPartitionsInValue(value, partition); + } +} + /** * Universal pre-execution param normalisation. * Fixes common AI mistakes without per-command logic. @@ -154,6 +202,8 @@ function normaliseInputParams( command: string, region: string, ): void { + normalizeArnPartitions(input, region); + for (const [key, value] of Object.entries(input)) { // Rule 1: Stringify any object param that AWS expects as a JSON string if ( diff --git a/apps/api/src/cloud-security/aws-partition.utils.spec.ts b/apps/api/src/cloud-security/aws-partition.utils.spec.ts new file mode 100644 index 0000000000..5f9f313092 --- /dev/null +++ b/apps/api/src/cloud-security/aws-partition.utils.spec.ts @@ -0,0 +1,73 @@ +import { + getAwsBaseCredentials, + getAwsDefaultRegion, + getAwsPartitionForRegion, + parseAwsRoleArn, + validateAwsPartitionConfig, +} from './aws-partition.utils'; + +describe('aws partition utils', () => { + it('uses GovCloud defaults for the aws-us-gov partition', () => { + expect(getAwsDefaultRegion('aws')).toBe('us-east-1'); + expect(getAwsDefaultRegion('aws-us-gov')).toBe('us-gov-west-1'); + expect(getAwsPartitionForRegion('us-east-1')).toBe('aws'); + expect(getAwsPartitionForRegion('us-gov-east-1')).toBe('aws-us-gov'); + }); + + it('parses commercial and GovCloud role ARNs', () => { + expect( + parseAwsRoleArn('arn:aws:iam::123456789012:role/CompAI-Auditor'), + ).toEqual({ partition: 'aws', accountId: '123456789012' }); + expect( + parseAwsRoleArn('arn:aws-us-gov:iam::123456789012:role/CompAI-Auditor'), + ).toEqual({ partition: 'aws-us-gov', accountId: '123456789012' }); + }); + + it('rejects mismatched role ARN and region partitions', () => { + expect( + validateAwsPartitionConfig({ + partition: 'aws-us-gov', + roleArn: 'arn:aws:iam::123456789012:role/CompAI-Auditor', + regions: ['us-gov-west-1', 'us-east-1'], + }), + ).toEqual([ + 'IAM Role ARN partition (aws) must match selected AWS environment (aws-us-gov).', + 'Selected regions do not match aws-us-gov: us-east-1.', + ]); + }); + + it('accepts commercial and GovCloud configurations independently', () => { + expect( + validateAwsPartitionConfig({ + partition: 'aws', + roleArn: 'arn:aws:iam::123456789012:role/CompAI-Auditor', + regions: ['us-east-1', 'us-west-2'], + }), + ).toEqual([]); + + expect( + validateAwsPartitionConfig({ + partition: 'aws-us-gov', + roleArn: 'arn:aws-us-gov:iam::123456789012:role/CompAI-Auditor', + regions: ['us-gov-west-1', 'us-gov-east-1'], + }), + ).toEqual([]); + }); + + it('uses explicit GovCloud base credentials when configured', () => { + process.env.SECURITY_HUB_GOVCLOUD_ACCESS_KEY_ID = 'AKIAGOV'; + process.env.SECURITY_HUB_GOVCLOUD_SECRET_ACCESS_KEY = 'secret'; + process.env.SECURITY_HUB_GOVCLOUD_SESSION_TOKEN = 'token'; + + expect(getAwsBaseCredentials('aws-us-gov')).toEqual({ + accessKeyId: 'AKIAGOV', + secretAccessKey: 'secret', + sessionToken: 'token', + }); + expect(getAwsBaseCredentials('aws')).toBeUndefined(); + + delete process.env.SECURITY_HUB_GOVCLOUD_ACCESS_KEY_ID; + delete process.env.SECURITY_HUB_GOVCLOUD_SECRET_ACCESS_KEY; + delete process.env.SECURITY_HUB_GOVCLOUD_SESSION_TOKEN; + }); +}); diff --git a/apps/api/src/cloud-security/aws-partition.utils.ts b/apps/api/src/cloud-security/aws-partition.utils.ts new file mode 100644 index 0000000000..ce0596b0c0 --- /dev/null +++ b/apps/api/src/cloud-security/aws-partition.utils.ts @@ -0,0 +1,100 @@ +import type { AwsCredentialIdentity } from '@aws-sdk/types'; + +export type AwsPartition = 'aws' | 'aws-us-gov'; + +const AWS_PARTITIONS = new Set(['aws', 'aws-us-gov']); + +export function normalizeAwsPartition(value: unknown): AwsPartition { + return typeof value === 'string' && AWS_PARTITIONS.has(value as AwsPartition) + ? (value as AwsPartition) + : 'aws'; +} + +export function getAwsDefaultRegion(partition: AwsPartition): string { + return partition === 'aws-us-gov' ? 'us-gov-west-1' : 'us-east-1'; +} + +export function getAwsPartitionForRegion(region: string): AwsPartition { + return region.startsWith('us-gov-') ? 'aws-us-gov' : 'aws'; +} + +export function getAwsRoleAssumerEnvName(partition: AwsPartition): string { + return partition === 'aws-us-gov' + ? 'SECURITY_HUB_GOVCLOUD_ROLE_ASSUMER_ARN' + : 'SECURITY_HUB_ROLE_ASSUMER_ARN'; +} + +export function getAwsRoleAssumerArn(partition: AwsPartition): string | undefined { + return process.env[getAwsRoleAssumerEnvName(partition)]; +} + +export function getAwsBaseCredentials( + partition: AwsPartition, +): AwsCredentialIdentity | undefined { + if (partition !== 'aws-us-gov') return undefined; + + const accessKeyId = process.env.SECURITY_HUB_GOVCLOUD_ACCESS_KEY_ID; + const secretAccessKey = + process.env.SECURITY_HUB_GOVCLOUD_SECRET_ACCESS_KEY; + if (!accessKeyId || !secretAccessKey) return undefined; + + return { + accessKeyId, + secretAccessKey, + sessionToken: process.env.SECURITY_HUB_GOVCLOUD_SESSION_TOKEN, + }; +} + +export function parseAwsRoleArn( + roleArn: string, +): { partition: AwsPartition; accountId: string } | null { + const match = roleArn.match(/^arn:(aws|aws-us-gov):iam::(\d{12}):role\/.+$/); + if (!match) return null; + + return { + partition: match[1] as AwsPartition, + accountId: match[2], + }; +} + +export function validateAwsPartitionConfig(params: { + partition: AwsPartition; + roleArn: string; + regions: string[]; + remediationRoleArn?: string; +}): string[] { + const errors: string[] = []; + const parsedRoleArn = parseAwsRoleArn(params.roleArn); + + if (!parsedRoleArn) { + errors.push( + 'Invalid IAM Role ARN format. Expected: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME or arn:aws-us-gov:iam::ACCOUNT_ID:role/ROLE_NAME', + ); + } else if (parsedRoleArn.partition !== params.partition) { + errors.push( + `IAM Role ARN partition (${parsedRoleArn.partition}) must match selected AWS environment (${params.partition}).`, + ); + } + + if (params.remediationRoleArn) { + const parsedRemediationArn = parseAwsRoleArn(params.remediationRoleArn); + if (!parsedRemediationArn) { + errors.push('Invalid Remediation Role ARN format.'); + } else if (parsedRemediationArn.partition !== params.partition) { + errors.push( + `Remediation Role ARN partition (${parsedRemediationArn.partition}) must match selected AWS environment (${params.partition}).`, + ); + } + } + + const mismatchedRegions = params.regions.filter( + (region) => getAwsPartitionForRegion(region) !== params.partition, + ); + if (mismatchedRegions.length > 0) { + errors.push( + `Selected regions do not match ${params.partition}: ${mismatchedRegions.join(', ')}.`, + ); + } + + return errors; +} diff --git a/apps/api/src/cloud-security/cloud-security-query.service.ts b/apps/api/src/cloud-security/cloud-security-query.service.ts index 9dea252410..1cdba70c00 100644 --- a/apps/api/src/cloud-security/cloud-security-query.service.ts +++ b/apps/api/src/cloud-security/cloud-security-query.service.ts @@ -31,6 +31,7 @@ export interface CloudProvider { variables: Record | null; requiredVariables: string[]; accountId?: string; + awsType?: string; regions?: string[]; tenantId?: string; subscriptionId?: string; @@ -133,6 +134,8 @@ export class CloudSecurityQueryService { typeof metadata.accountId === 'string' ? metadata.accountId : undefined, + awsType: + typeof metadata.awsType === 'string' ? metadata.awsType : undefined, regions: Array.isArray(metadata.regions) ? metadata.regions.filter((r): r is string => typeof r === 'string') : undefined, @@ -171,6 +174,8 @@ export class CloudSecurityQueryService { typeof settings.accountId === 'string' ? settings.accountId : undefined, + awsType: + typeof settings.awsType === 'string' ? settings.awsType : undefined, regions: Array.isArray(settings.regions) ? settings.regions.filter((r): r is string => typeof r === 'string') : undefined, diff --git a/apps/api/src/cloud-security/providers/aws-security.service.ts b/apps/api/src/cloud-security/providers/aws-security.service.ts index a00b676e67..bf6c825935 100644 --- a/apps/api/src/cloud-security/providers/aws-security.service.ts +++ b/apps/api/src/cloud-security/providers/aws-security.service.ts @@ -5,6 +5,17 @@ import { GetCostAndUsageCommand, } from '@aws-sdk/client-cost-explorer'; import type { SecurityFinding } from '../cloud-security.service'; +import { + type AwsPartition, + getAwsBaseCredentials, + getAwsDefaultRegion, + getAwsPartitionForRegion, + getAwsRoleAssumerArn, + getAwsRoleAssumerEnvName, + normalizeAwsPartition, + parseAwsRoleArn, + validateAwsPartitionConfig, +} from '../aws-partition.utils'; import type { AwsCredentials, AwsServiceAdapter, @@ -54,6 +65,8 @@ import { TransferFamilyAdapter } from './aws/transfer-family.adapter'; import { ElasticBeanstalkAdapter } from './aws/elastic-beanstalk.adapter'; import { AppFlowAdapter } from './aws/appflow.adapter'; +const GOVCLOUD_UNSUPPORTED_SERVICE_IDS = new Set(['cloudfront', 'shield']); + @Injectable() export class AWSSecurityService { private readonly logger = new Logger(AWSSecurityService.name); @@ -121,20 +134,45 @@ export class AWSSecurityService { ); } - const configuredRegions = this.getConfiguredRegions(credentials, variables); + const partition = normalizeAwsPartition( + credentials.awsType ?? variables.awsType, + ); + const configuredRegions = this.getConfiguredRegions( + credentials, + variables, + partition, + ); const primaryRegion = configuredRegions[0]; + const mismatchedRegions = configuredRegions.filter( + (region) => getAwsPartitionForRegion(region) !== partition, + ); + if (mismatchedRegions.length > 0) { + throw new Error( + `AWS regions do not match selected environment (${partition}): ${mismatchedRegions.join(', ')}`, + ); + } this.logger.log( - `Scanning ${configuredRegions.length} region(s): ${configuredRegions.join(', ')}`, + `Scanning ${configuredRegions.length} ${partition} region(s): ${configuredRegions.join(', ')}`, ); // Assume role ONCE — IAM is global, credentials work across all regions let awsCredentials: AwsCredentials; if (isRoleAuth) { + const partitionErrors = validateAwsPartitionConfig({ + partition, + roleArn: credentials.roleArn as string, + regions: configuredRegions, + }); + if (partitionErrors.length > 0) { + throw new Error(partitionErrors.join(' ')); + } + awsCredentials = await this.assumeRole({ roleArn: credentials.roleArn as string, externalId: credentials.externalId as string, region: primaryRegion, + partition, }); } else { awsCredentials = { @@ -144,10 +182,16 @@ export class AWSSecurityService { } // undefined = scan all (no detection data), [] = scan nothing (all disabled), [...] = scan specific - const activeAdapters = + const activeAdaptersBeforePartitionFilter = enabledServices === undefined ? this.adapters : this.adapters.filter((a) => enabledServices.includes(a.serviceId)); + const activeAdapters = + partition === 'aws-us-gov' + ? activeAdaptersBeforePartitionFilter.filter( + (adapter) => !GOVCLOUD_UNSUPPORTED_SERVICE_IDS.has(adapter.serviceId), + ) + : activeAdaptersBeforePartitionFilter; this.logger.log( `Scanning ${activeAdapters.length} service adapters` + @@ -231,6 +275,7 @@ export class AWSSecurityService { private getConfiguredRegions( credentials: Record, variables: Record, + partition: AwsPartition, ): string[] { if (Array.isArray(credentials.regions) && credentials.regions.length > 0) { const filtered = credentials.regions.filter( @@ -257,7 +302,7 @@ export class AWSSecurityService { return [singleRegion.trim()]; } - return ['us-east-1']; + return [getAwsDefaultRegion(partition)]; } /** @@ -277,11 +322,18 @@ export class AWSSecurityService { ); } + const partition = normalizeAwsPartition( + credentials.awsType ?? + parseAwsRoleArn(remediationRoleArn)?.partition ?? + getAwsPartitionForRegion(region), + ); + return this.assumeRole({ roleArn: remediationRoleArn, externalId: credentials.externalId as string, region, sessionName: 'CompSecurityRemediation', + partition, }); } @@ -293,18 +345,38 @@ export class AWSSecurityService { externalId: string; region: string; sessionName?: string; + partition?: AwsPartition; }): Promise { const { roleArn, externalId, region, sessionName } = params; + const parsedRoleArn = parseAwsRoleArn(roleArn); + const partition = + params.partition ?? parsedRoleArn?.partition ?? getAwsPartitionForRegion(region); - const roleAssumerArn = process.env.SECURITY_HUB_ROLE_ASSUMER_ARN; + if (!parsedRoleArn) { + throw new Error( + 'Invalid IAM Role ARN format. Expected arn:aws:iam::... or arn:aws-us-gov:iam::...', + ); + } + + if (parsedRoleArn.partition !== partition) { + throw new Error( + `Role ARN partition (${parsedRoleArn.partition}) does not match selected AWS environment (${partition}).`, + ); + } + + const roleAssumerArn = getAwsRoleAssumerArn(partition); if (!roleAssumerArn) { + const envName = getAwsRoleAssumerEnvName(partition); throw new Error( - 'Missing SECURITY_HUB_ROLE_ASSUMER_ARN (our roleAssumer ARN).', + `Missing ${envName} (our ${partition} roleAssumer ARN).`, ); } // Hop 1: task role -> roleAssumer - const baseSts = new STSClient({ region }); + const baseSts = new STSClient({ + region, + credentials: getAwsBaseCredentials(partition), + }); const roleAssumerResp = await baseSts.send( new AssumeRoleCommand({ RoleArn: roleAssumerArn, @@ -363,8 +435,23 @@ export class AWSSecurityService { credentials: Record, variables: Record, ): Promise { - const configuredRegions = this.getConfiguredRegions(credentials, variables); + const partition = normalizeAwsPartition( + credentials.awsType ?? variables.awsType, + ); + const configuredRegions = this.getConfiguredRegions( + credentials, + variables, + partition, + ); const primaryRegion = configuredRegions[0]; + const mismatchedRegions = configuredRegions.filter( + (region) => getAwsPartitionForRegion(region) !== partition, + ); + if (mismatchedRegions.length > 0) { + throw new Error( + `AWS regions do not match selected environment (${partition}): ${mismatchedRegions.join(', ')}`, + ); + } const isRoleAuth = Boolean(credentials.roleArn && credentials.externalId); const isKeyAuth = Boolean( @@ -381,6 +468,7 @@ export class AWSSecurityService { roleArn: credentials.roleArn as string, externalId: credentials.externalId as string, region: primaryRegion, + partition, }); } else { awsCredentials = { @@ -390,7 +478,7 @@ export class AWSSecurityService { } const client = new CostExplorerClient({ - region: 'us-east-1', // Cost Explorer is global, always use us-east-1 + region: getAwsDefaultRegion(partition), credentials: awsCredentials, }); @@ -436,6 +524,12 @@ export class AWSSecurityService { )) { if (activeAwsNames.has(awsName)) { for (const id of serviceIds) { + if ( + partition === 'aws-us-gov' && + GOVCLOUD_UNSUPPORTED_SERVICE_IDS.has(id) + ) { + continue; + } if (!detected.includes(id)) { detected.push(id); } diff --git a/apps/api/src/cloud-security/remediation.service.ts b/apps/api/src/cloud-security/remediation.service.ts index 65d257bbad..d53d2d869c 100644 --- a/apps/api/src/cloud-security/remediation.service.ts +++ b/apps/api/src/cloud-security/remediation.service.ts @@ -11,6 +11,10 @@ import { executePlanSteps, validatePlanSteps, } from './aws-command-executor'; +import { + getAwsDefaultRegion, + normalizeAwsPartition, +} from './aws-partition.utils'; import type { FixPlan, AwsCommandStep } from './ai-remediation.prompt'; @Injectable() @@ -891,7 +895,7 @@ export class RemediationService { if (Array.isArray(credentials.regions) && credentials.regions.length > 0) { return credentials.regions[0] as string; } - return 'us-east-1'; + return getAwsDefaultRegion(normalizeAwsPartition(credentials.awsType)); } /** diff --git a/apps/api/src/integration-platform/controllers/connections.controller.ts b/apps/api/src/integration-platform/controllers/connections.controller.ts index 440ad91fa9..883177d315 100644 --- a/apps/api/src/integration-platform/controllers/connections.controller.ts +++ b/apps/api/src/integration-platform/controllers/connections.controller.ts @@ -39,6 +39,14 @@ import { type TaskTemplateId, type IntegrationCredentials, } from '@trycompai/integration-platform'; +import { + getAwsBaseCredentials, + getAwsRoleAssumerArn, + getAwsRoleAssumerEnvName, + normalizeAwsPartition, + parseAwsRoleArn, + validateAwsPartitionConfig, +} from '../../cloud-security/aws-partition.utils'; interface CreateConnectionDto { providerSlug: string; @@ -339,12 +347,15 @@ export class ConnectionsController { const updates: Record = {}; if (typeof creds.roleArn === 'string') { updates.roleArn = creds.roleArn; - const m = creds.roleArn.match(/^arn:aws:iam::(\d{12}):role\/.+$/); - if (m) updates.accountId = m[1]; + const parsedRoleArn = parseAwsRoleArn(creds.roleArn); + if (parsedRoleArn) updates.accountId = parsedRoleArn.accountId; } if (typeof creds.remediationRoleArn === 'string') { updates.remediationRoleArn = creds.remediationRoleArn; } + if (typeof creds.awsType === 'string') { + updates.awsType = creds.awsType; + } if (Array.isArray(creds.regions)) { updates.regions = creds.regions; } @@ -446,6 +457,9 @@ export class ConnectionsController { if (typeof credentials.connectionName === 'string') { metadata.connectionName = credentials.connectionName; } + if (typeof credentials.awsType === 'string') { + metadata.awsType = credentials.awsType; + } if (Array.isArray(credentials.regions)) { metadata.regions = credentials.regions; } @@ -453,13 +467,8 @@ export class ConnectionsController { // These are not secrets - roleArn is visible in AWS console, externalId is typically the org ID if (typeof credentials.roleArn === 'string') { metadata.roleArn = credentials.roleArn; - // Extract account ID from ARN: arn:aws:iam::123456789012:role/RoleName - const arnMatch = credentials.roleArn.match( - /^arn:aws:iam::(\d{12}):role\/.+$/, - ); - if (arnMatch) { - metadata.accountId = arnMatch[1]; - } + const parsedRoleArn = parseAwsRoleArn(credentials.roleArn); + if (parsedRoleArn) metadata.accountId = parsedRoleArn.accountId; } if (typeof credentials.externalId === 'string') { metadata.externalId = credentials.externalId; @@ -537,6 +546,7 @@ export class ConnectionsController { const roleArnValue = credentials.roleArn; const externalIdValue = credentials.externalId; const regionsValue = credentials.regions; + const partition = normalizeAwsPartition(credentials.awsType); if (typeof roleArnValue !== 'string' || !roleArnValue.trim()) { return { success: false, message: 'Missing or invalid IAM Role ARN' }; @@ -559,20 +569,32 @@ export class ConnectionsController { return { success: false, message: 'No valid AWS regions selected' }; } - // Validate ARN format - const arnMatch = roleArn.match(/^arn:aws:iam::(\d{12}):role\/.+$/); - if (!arnMatch) { + const partitionErrors = validateAwsPartitionConfig({ + partition, + roleArn, + regions, + remediationRoleArn: + typeof credentials.remediationRoleArn === 'string' + ? credentials.remediationRoleArn.trim() + : undefined, + }); + if (partitionErrors.length > 0) { + return { success: false, message: partitionErrors.join(' ') }; + } + + const parsedRoleArn = parseAwsRoleArn(roleArn); + if (!parsedRoleArn) { return { success: false, - message: - 'Invalid IAM Role ARN format. Expected: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME', + message: 'Invalid IAM Role ARN format.', }; } - const roleAssumerArn = process.env.SECURITY_HUB_ROLE_ASSUMER_ARN; + const roleAssumerArn = getAwsRoleAssumerArn(partition); if (!roleAssumerArn) { + const envName = getAwsRoleAssumerEnvName(partition); this.logger.error( - 'Missing SECURITY_HUB_ROLE_ASSUMER_ARN environment variable', + `Missing ${envName} environment variable`, ); return { success: false, @@ -585,7 +607,10 @@ export class ConnectionsController { try { // Step 1: Assume our role assumer role this.logger.log('Validating AWS: Assuming role assumer...'); - const baseSts = new STSClient({ region: primaryRegion }); + const baseSts = new STSClient({ + region: primaryRegion, + credentials: getAwsBaseCredentials(partition), + }); const roleAssumerResp = await baseSts.send( new AssumeRoleCommand({ RoleArn: roleAssumerArn, @@ -657,7 +682,7 @@ export class ConnectionsController { return { success: true, message, - details: { account: identity.Account, regions }, + details: { account: identity.Account ?? parsedRoleArn.accountId, regions }, }; } catch (err) { const errorMessage = @@ -1183,14 +1208,15 @@ export class ConnectionsController { const metaUpdates: Record = {}; if (typeof mergedCredentials.roleArn === 'string') { metaUpdates.roleArn = mergedCredentials.roleArn; - const arnMatch = mergedCredentials.roleArn.match( - /^arn:aws:iam::(\d{12}):role\/.+$/, - ); - if (arnMatch) metaUpdates.accountId = arnMatch[1]; + const parsedRoleArn = parseAwsRoleArn(mergedCredentials.roleArn); + if (parsedRoleArn) metaUpdates.accountId = parsedRoleArn.accountId; } if (typeof mergedCredentials.remediationRoleArn === 'string') { metaUpdates.remediationRoleArn = mergedCredentials.remediationRoleArn; } + if (typeof mergedCredentials.awsType === 'string') { + metaUpdates.awsType = mergedCredentials.awsType; + } if (Array.isArray(mergedCredentials.regions)) { metaUpdates.regions = mergedCredentials.regions; } diff --git a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx index 636ec4063c..7e70f73215 100644 --- a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx +++ b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx @@ -29,7 +29,11 @@ import { X, Zap, } from 'lucide-react'; -import { awsRemediationScript } from '@trycompai/integration-platform'; +import { + getAwsCloudShellUrl, + getAwsRemediationScript, + normalizeAwsEnvironment, +} from '@trycompai/integration-platform'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { getActiveBatch } from '../actions/batch-fix'; @@ -63,6 +67,7 @@ interface CloudTestsSectionProps { lastRunAt?: Date | null; /** Connection variables (e.g., GCP org ID) */ variables?: Record; + awsType?: string; } const SEVERITY_ORDER: Record = { @@ -155,6 +160,7 @@ export function CloudTestsSection({ orgId, lastRunAt, variables, + awsType, }: CloudTestsSectionProps) { const api = useApi(); const [scanCompleted, setScanCompleted] = useState(false); @@ -999,6 +1005,7 @@ export function CloudTestsSection({ onOpenChange={setShowSetupDialog} orgId={orgId} connectionId={connectionId} + awsType={awsType} onSaved={() => { setShowSetupDialog(false); // Reload capabilities after role ARN is saved @@ -1047,12 +1054,14 @@ function RemediationSetupDialog({ onOpenChange, orgId, connectionId, + awsType, onSaved, }: { open: boolean; onOpenChange: (open: boolean) => void; orgId: string; connectionId: string; + awsType?: string; onSaved?: () => void; }) { const api = useApi(); @@ -1060,11 +1069,13 @@ function RemediationSetupDialog({ const [roleArn, setRoleArn] = useState(''); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); + const awsEnvironment = normalizeAwsEnvironment(awsType); - const finalScript = awsRemediationScript.replace( + const finalScript = getAwsRemediationScript(awsEnvironment).replace( /YOUR_EXTERNAL_ID/g, orgId, ); + const cloudShellUrl = getAwsCloudShellUrl(awsEnvironment); const handleCopy = useCallback(() => { navigator.clipboard.writeText(finalScript); @@ -1075,9 +1086,9 @@ function RemediationSetupDialog({ const handleSaveRoleArn = useCallback(async () => { if (!roleArn.trim() || !connectionId) return; - const arnPattern = /^arn:aws:iam::\d{12}:role\/.+$/; + const arnPattern = /^arn:(aws|aws-us-gov):iam::\d{12}:role\/.+$/; if (!arnPattern.test(roleArn.trim())) { - setSaveError('Invalid ARN format. Expected: arn:aws:iam:::role/'); + setSaveError('Invalid ARN format. Expected an AWS IAM role ARN.'); return; } @@ -1164,7 +1175,7 @@ function RemediationSetupDialog({ )} diff --git a/apps/app/src/app/(app)/[orgId]/cloud-tests/types.ts b/apps/app/src/app/(app)/[orgId]/cloud-tests/types.ts index b78b72879b..7429a456c2 100644 --- a/apps/app/src/app/(app)/[orgId]/cloud-tests/types.ts +++ b/apps/app/src/app/(app)/[orgId]/cloud-tests/types.ts @@ -32,6 +32,7 @@ export interface Provider { variables?: Record | null; requiredVariables?: string[]; accountId?: string; + awsType?: string; regions?: string[]; tenantId?: string; subscriptionId?: string; diff --git a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.test.tsx b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.test.tsx index 846029b15b..f0a1ff6f74 100644 --- a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.test.tsx +++ b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.test.tsx @@ -42,6 +42,11 @@ vi.mock('lucide-react', () => ({ vi.mock('@trycompai/integration-platform', () => ({ awsRemediationScript: '', + getAwsCloudShellUrl: () => 'https://console.aws.amazon.com/cloudshell', + getAwsCloudShellScript: () => '', + getAwsRemediationScript: () => '', + normalizeAwsEnvironment: (value: unknown) => + value === 'aws-us-gov' ? 'aws-us-gov' : 'aws', })); vi.mock('sonner', () => ({ diff --git a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx index 303d854cc2..38e9b7081a 100644 --- a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx +++ b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx @@ -5,7 +5,12 @@ import { CredentialInput } from '@/components/integrations/CredentialInput'; import type { IntegrationProvider } from '@/hooks/use-integration-platform'; import { useIntegrationMutations } from '@/hooks/use-integration-platform'; import { Button, Label } from '@trycompai/design-system'; -import { awsRemediationScript } from '@trycompai/integration-platform'; +import { + getAwsCloudShellUrl, + getAwsCloudShellScript, + getAwsRemediationScript, + normalizeAwsEnvironment, +} from '@trycompai/integration-platform'; import { ArrowRight, Shield } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; import { toast } from 'sonner'; @@ -28,11 +33,15 @@ function FieldRow({ value, error, onChange, + optionsOverride, + disabled, }: { field: { id: string; label: string; required?: boolean; helpText?: string; type?: string }; value: string | string[]; error?: string; onChange: (value: string | string[]) => void; + optionsOverride?: { value: string; label: string }[]; + disabled?: boolean; }) { return (
@@ -44,17 +53,25 @@ function FieldRow({ field={field as Parameters[0]['field']} value={value} onChange={onChange} + optionsOverride={optionsOverride} + disabled={disabled} /> - {field.helpText && ( -

{field.helpText}

- )} + {field.helpText &&

{field.helpText}

} {error &&

{error}

}
); } /** Compact setup guide — shows only headings as collapsible sections, max 3-4 key steps each. */ -function SetupGuide({ text, fallback, docsUrl }: { text?: string | null; fallback: string; docsUrl?: string | null }) { +function SetupGuide({ + text, + fallback, + docsUrl, +}: { + text?: string | null; + fallback: string; + docsUrl?: string | null; +}) { const raw = text || fallback; const [expandedSection, setExpandedSection] = useState(null); @@ -85,9 +102,7 @@ function SetupGuide({ text, fallback, docsUrl }: { text?: string | null; fallbac // No structured content — simple fallback if (sections.length === 0) { - return ( -

{formatInline(raw)}

- ); + return

{formatInline(raw)}

; } return ( @@ -105,7 +120,10 @@ function SetupGuide({ text, fallback, docsUrl }: { text?: string | null; fallbac > @@ -118,7 +136,7 @@ function SetupGuide({ text, fallback, docsUrl }: { text?: string | null; fallbac {isOpen && (
- {(previewSteps).map((step, j) => ( + {previewSteps.map((step, j) => (
{j + 1} @@ -130,7 +148,8 @@ function SetupGuide({ text, fallback, docsUrl }: { text?: string | null; fallbac ))} {section.steps.length > 3 && (

- +{section.steps.length - 3} more step{section.steps.length - 3 !== 1 ? 's' : ''} in docs + +{section.steps.length - 3} more step{section.steps.length - 3 !== 1 ? 's' : ''}{' '} + in docs

)}
@@ -158,14 +177,35 @@ function formatInline(text: string): React.ReactNode { const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g); return parts.map((part, i) => { if (part.startsWith('**') && part.endsWith('**')) { - return {part.slice(2, -2)}; + return ( + + {part.slice(2, -2)} + + ); } if (part.startsWith('`') && part.endsWith('`')) { - return {part.slice(1, -1)}; + return ( + + {part.slice(1, -1)} + + ); } const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/); if (linkMatch) { - return
{linkMatch[1]}; + return ( + + {linkMatch[1]} + + ); } return {part}; }); @@ -203,23 +243,11 @@ export function EmptyStateOnboarding({ // Cloud providers with setup scripts get the full guided flow if (isCloudProvider && provider.setupScript) { - return ( - - ); + return ; } // Everything else: API key / basic / custom credentials - return ( - - ); + return ; } // ─── OAuth (GitHub, Google Workspace, etc.) ───────────────────────────── @@ -261,9 +289,7 @@ function OAuthSetup({
- {provider.logoUrl && ( - - )} + {provider.logoUrl && }

Connect {provider.name}

@@ -335,7 +361,11 @@ function CredentialSetup({ const hasConfigurableFields = fields.length > 0; const updateCredential = (fieldId: string, value: string | string[]) => { - setCredentials((prev) => ({ ...prev, [fieldId]: value })); + setCredentials((prev) => ({ + ...prev, + [fieldId]: value, + ...(fieldId === 'awsType' ? { regions: [] } : {}), + })); if (errors[fieldId]) { setErrors((prev) => { const next = { ...prev }; @@ -423,7 +453,9 @@ function CredentialSetup({

-
+
{/* ─── Left: Unified setup flow ─── */}
- {/* Step 1 */} + {awsTypeFields.length > 0 && ( +
+ {/* Step 1 */} + + {awsTypeFields.map((field) => ( + updateCredential(field.id, v)} + /> + ))} +
+ )} + {/* Step 2 */} {provider.setupScript && (
- - + +

- Connecting multiple accounts? Run the script in each account and add them one by one. + Connecting multiple accounts? Run the script in each account and add them one by + one.

)}
- {/* Step 2 */} + {/* Step 3 */}
- + {connectionFields.map((field) => ( - {/* Step 3 */} + {/* Step 4 */} {regionFields.length > 0 && ( <>
- + {regionFields.map((field) => ( updateCredential(field.id, v)} + optionsOverride={filteredRegionOptions} + disabled={!hasSelectedAwsEnvironment} /> ))}
@@ -594,7 +673,9 @@ function CloudSetup({ {/* CTA */}

- Enable one-click fixes for security findings. This creates a separate write-access role — your audit role stays read-only. + Enable one-click fixes for security findings. This creates a separate write-access + role — your audit role stays read-only.

; const displayName = @@ -51,13 +57,40 @@ export function AwsAccountSettingsBody({ const externalId = (metadata.externalId as string) ?? orgId; const hasRemediation = Boolean(metadata.remediationRoleArn); const regionsField = provider.credentialFields?.find((f) => f.id === 'regions'); + const awsEnvironment = normalizeAwsEnvironment(awsType); + const remediationScript = getAwsRemediationScript(awsEnvironment); + const cloudShellUrl = getAwsCloudShellUrl(awsEnvironment); + const filteredRegionOptions = + regionsField?.options?.filter((option) => + awsEnvironment === 'aws-us-gov' + ? option.value.startsWith('us-gov-') + : !option.value.startsWith('us-gov-'), + ) ?? []; useEffect(() => { if (!connection) return; + const nextAwsType = typeof metadata.awsType === 'string' ? metadata.awsType : 'aws'; setRoleArn((metadata.roleArn as string) ?? ''); setRemediationRoleArn((metadata.remediationRoleArn as string) ?? ''); - setRegions(Array.isArray(metadata.regions) ? (metadata.regions as string[]) : []); - }, [connection, metadata.roleArn, metadata.remediationRoleArn, metadata.regions]); + setRegions( + Array.isArray(metadata.regions) + ? (metadata.regions as string[]).filter((region) => + nextAwsType === 'aws-us-gov' + ? region.startsWith('us-gov-') + : !region.startsWith('us-gov-'), + ) + : [], + ); + setAwsType(nextAwsType); + + console.log('awsType', nextAwsType, metadata); + }, [ + connection, + metadata.roleArn, + metadata.remediationRoleArn, + metadata.regions, + metadata.awsType, + ]); const saveField = useCallback( async ( @@ -92,20 +125,36 @@ export function AwsAccountSettingsBody({ toast.error('Role ARN is required'); return; } + const expectedPrefix = + awsEnvironment === 'aws-us-gov' + ? 'arn:aws-us-gov:iam::' + : 'arn:aws:iam::'; + if (!roleArn.startsWith(expectedPrefix)) { + toast.error('Role ARN must match the selected AWS environment'); + return; + } const meta: Record = { roleArn }; - const arnMatch = roleArn.match(/^arn:aws:iam::(\d{12}):role\/.+$/); + const arnMatch = roleArn.match(/^arn:(?:aws|aws-us-gov):iam::(\d{12}):role\/.+$/); if (arnMatch) meta.accountId = arnMatch[1]; await saveField({ roleArn }, meta, setSavingCredentials, 'Credentials saved'); - }, [roleArn, saveField]); + }, [awsEnvironment, roleArn, saveField]); const handleSaveRemediation = useCallback(async () => { + const expectedPrefix = + awsEnvironment === 'aws-us-gov' + ? 'arn:aws-us-gov:iam::' + : 'arn:aws:iam::'; + if (remediationRoleArn && !remediationRoleArn.startsWith(expectedPrefix)) { + toast.error('Remediation Role ARN must match the selected AWS environment'); + return; + } await saveField( { remediationRoleArn }, { remediationRoleArn }, setSavingRemediation, 'Remediation role saved', ); - }, [remediationRoleArn, saveField]); + }, [awsEnvironment, remediationRoleArn, saveField]); const handleSaveRegions = useCallback(async () => { if (regions.length === 0) { @@ -115,6 +164,20 @@ export function AwsAccountSettingsBody({ await saveField({ regions }, { regions }, setSavingRegions, 'Regions saved'); }, [regions, saveField]); + const handleSaveAwsType = useCallback(async () => { + if (!awsType) { + toast.error('Select an AWS environment'); + return; + } + await saveField( + { awsType, regions: [] }, + { awsType, regions: [] }, + setSavingAwsType, + 'AWS environment saved', + ); + setRegions([]); + }, [awsType, saveField]); + const handleDisconnect = useCallback(async () => { if (!confirm('Are you sure? All associated data will be removed.')) return; setDisconnecting(true); @@ -160,9 +223,7 @@ export function AwsAccountSettingsBody({ ) } /> - {accountId && ( - - )} + {accountId && } {displayName && !accountId && ( )} @@ -181,6 +242,35 @@ export function AwsAccountSettingsBody({ )}
+ + + setAwsType(v as string)} + /> + + + + setRegions(v as string[])} + optionsOverride={filteredRegionOptions} />
-            {expanded ? finalScript : previewLines}
+            {disabled ? disabledMessage : expanded ? finalScript : previewLines}
           
{!expanded && ( )} - {provider?.setupScript && ( - + {setupScript && ( + )} {!provider?.setupScript && provider?.setupInstructions && (
@@ -570,8 +617,10 @@ export function ConnectIntegrationDialog({
updateCredential(field.id, value)} + optionsOverride={field.id === 'regions' ? filteredRegionOptions : undefined} + disabled={field.id === 'regions' && !hasSelectedAwsEnvironment} /> {field.helpText && (

{field.helpText}

@@ -640,6 +691,8 @@ export function ConnectIntegrationDialog({ field={field} value={credentials[field.id] || (field.type === 'multi-select' ? [] : '')} onChange={(value) => updateCredential(field.id, value)} + optionsOverride={field.id === 'regions' ? filteredRegionOptions : undefined} + disabled={field.id === 'regions' && !hasSelectedAwsEnvironment} /> {field.helpText &&

{field.helpText}

}
diff --git a/apps/app/src/components/integrations/CredentialInput.tsx b/apps/app/src/components/integrations/CredentialInput.tsx index eac5e8951d..a134a606be 100644 --- a/apps/app/src/components/integrations/CredentialInput.tsx +++ b/apps/app/src/components/integrations/CredentialInput.tsx @@ -4,12 +4,14 @@ import type { CredentialField } from '@/hooks/use-integration-platform'; import { ComboboxDropdown } from '@trycompai/ui/combobox-dropdown'; import MultipleSelector from '@trycompai/ui/multiple-selector'; import { - Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, +} from '@trycompai/ui/select'; +import { + Input, Textarea, } from '@trycompai/design-system'; import { Eye, EyeOff } from 'lucide-react'; @@ -19,10 +21,14 @@ export function CredentialInput({ field, value, onChange, + optionsOverride, + disabled = false, }: { field: CredentialField; value: string | string[]; onChange: (value: string | string[]) => void; + optionsOverride?: { value: string; label: string }[]; + disabled?: boolean; }) { const [showPassword, setShowPassword] = useState(false); const handleChange = (e: React.ChangeEvent) => @@ -62,13 +68,19 @@ export function CredentialInput({ } if (field.type === 'select') { + const options = optionsOverride ?? field.options ?? []; + return ( - { if (v) onChange(v); }} + disabled={disabled} + > - {field.options?.map((opt) => ( + {options.map((opt) => ( {opt.label} @@ -109,7 +121,7 @@ export function CredentialInput({ if (field.type === 'multi-select') { const selectedValues = Array.isArray(value) ? value : []; - const options = field.options ?? []; + const options = optionsOverride ?? field.options ?? []; return ( ({ value: opt.value, label: opt.label }))} options={options.map((opt) => ({ value: opt.value, label: opt.label }))} placeholder={field.placeholder || 'Select...'} + disabled={disabled} creatable={options.length === 0} emptyIndicator={

No options

} /> diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 0bb19f72aa..7b7d0c913b 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -2037,6 +2037,37 @@ ] } }, + "/v1/people/{id}/resend-portal-invite": { + "post": { + "operationId": "PeopleController_resendPortalInvite_v1", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "description": "Member ID", + "schema": { + "example": "mem_abc123def456", + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "" + } + }, + "security": [ + { + "apikey": [] + } + ], + "summary": "Resend portal invite email to a member", + "tags": [ + "People" + ] + } + }, "/v1/people/{id}/unlink-device": { "patch": { "description": "Resets the fleetDmLabelId for a member, effectively unlinking their device from FleetDM. This will disconnect the device from the organization. Supports both API key authentication (X-API-Key header) and session authentication (Bearer token or cookies).", @@ -21970,6 +22001,10 @@ "items": { "type": "string" } + }, + "sendPortalEmail": { + "type": "boolean", + "example": false } }, "required": [ diff --git a/packages/integration-platform/src/index.ts b/packages/integration-platform/src/index.ts index f49ce5f57f..42382cd7a5 100644 --- a/packages/integration-platform/src/index.ts +++ b/packages/integration-platform/src/index.ts @@ -133,7 +133,14 @@ export { manifest as githubManifest } from './manifests/github'; export { matchesSyncFilterTerms, parseSyncFilterTerms } from './sync-filter/email-exclusion-terms'; // AWS credential helpers (used by frontend setup dialogs) -export { awsRemediationScript } from './manifests/aws/credentials'; +export { + awsRemediationScript, + getAwsCloudShellUrl, + getAwsCloudShellScript, + getAwsRemediationScript, + normalizeAwsEnvironment, +} from './manifests/aws/credentials'; +export type { AwsEnvironment } from './manifests/aws/credentials'; // API Response types (for frontend and API type sharing) diff --git a/packages/integration-platform/src/manifests/aws/credentials.ts b/packages/integration-platform/src/manifests/aws/credentials.ts index 252cbccece..da2537afe0 100644 --- a/packages/integration-platform/src/manifests/aws/credentials.ts +++ b/packages/integration-platform/src/manifests/aws/credentials.ts @@ -1,9 +1,52 @@ import { z } from 'zod'; +export type AwsEnvironment = 'aws' | 'aws-us-gov'; + +const COMP_AI_COMMERCIAL_ROLE_ASSUMER_ACCOUNT_ID = '684120556289'; +const COMP_AI_GOVCLOUD_ROLE_ASSUMER_ACCOUNT_ID = '633779453318'; + +export function normalizeAwsEnvironment(value: unknown): AwsEnvironment { + return value === 'aws-us-gov' ? 'aws-us-gov' : 'aws'; +} + +function getAwsRoleAssumerArn(environment: AwsEnvironment): string { + const accountId = + environment === 'aws-us-gov' + ? COMP_AI_GOVCLOUD_ROLE_ASSUMER_ACCOUNT_ID + : COMP_AI_COMMERCIAL_ROLE_ASSUMER_ACCOUNT_ID; + + return `arn:${environment}:iam::${accountId}:role/roleAssumer`; +} + +function getAwsManagedPolicyArn( + environment: AwsEnvironment, + policyName: string, +): string { + return `arn:${environment}:iam::aws:policy/${policyName}`; +} + +export function getAwsCloudShellUrl(environment: AwsEnvironment = 'aws'): string { + return environment === 'aws-us-gov' + ? 'https://console.amazonaws-us-gov.com/cloudshell' + : 'https://console.aws.amazon.com/cloudshell'; +} + /** * AWS credential fields for the connection form */ export const awsCredentialFields = [ + { + id: 'awsType', + label: 'AWS Environment', + type: 'select' as const, + required: true, + placeholder: 'Select AWS environment', + helpText: 'Choose the AWS partition where this account runs.', + options: [ + { value: 'aws', label: 'Commercial AWS' }, + { value: 'aws-us-gov', label: 'AWS GovCloud (US)' }, + ], + }, { id: 'connectionName', label: 'Connection Name', @@ -51,6 +94,9 @@ export const awsCredentialFields = [ { value: 'us-east-2', label: 'us-east-2 (Ohio)' }, { value: 'us-west-1', label: 'us-west-1 (N. California)' }, { value: 'us-west-2', label: 'us-west-2 (Oregon)' }, + // AWS GovCloud (US) Regions + { value: 'us-gov-west-1', label: 'us-gov-west-1 (GovCloud US-West)' }, + { value: 'us-gov-east-1', label: 'us-gov-east-1 (GovCloud US-East)' }, // Europe Regions { value: 'eu-west-1', label: 'eu-west-1 (Ireland)' }, { value: 'eu-west-2', label: 'eu-west-2 (London)' }, @@ -92,18 +138,19 @@ export const awsCredentialFields = [ * Validation schema for AWS credentials */ export const awsCredentialSchema = z.object({ + awsType: z.enum(['aws', 'aws-us-gov']), connectionName: z.string().min(1, 'Connection name is required'), roleArn: z .string() .regex( - /^arn:aws:iam::\d{12}:role\/.+$/, - 'Must be a valid IAM Role ARN (arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME)', + /^arn:(aws|aws-us-gov):iam::\d{12}:role\/.+$/, + 'Must be a valid IAM Role ARN', ), externalId: z.string().min(1), remediationRoleArn: z .string() .regex( - /^arn:aws:iam::\d{12}:role\/.+$/, + /^arn:(aws|aws-us-gov):iam::\d{12}:role\/.+$/, 'Must be a valid IAM Role ARN', ) .optional() @@ -115,8 +162,20 @@ export const awsCredentialSchema = z.object({ * CloudShell setup script for customers to create the IAM role. * Customers run this in AWS CloudShell with their External ID as argument. */ -export const awsCloudShellScript = [ +export function getAwsCloudShellScript(environment: AwsEnvironment = 'aws'): string { + const roleAssumerArn = getAwsRoleAssumerArn(environment); + const securityAuditPolicyArn = getAwsManagedPolicyArn( + environment, + 'SecurityAudit', + ); + const viewOnlyPolicyArn = getAwsManagedPolicyArn( + environment, + 'job-function/ViewOnlyAccess', + ); + + return [ '#!/bin/bash', + '(', 'set -euo pipefail', '', 'ROLE_NAME="CompAI-Auditor"', @@ -129,7 +188,7 @@ export const awsCloudShellScript = [ ' "Version": "2012-10-17",', ' "Statement": [{', ' "Effect": "Allow",', - ' "Principal": { "AWS": "arn:aws:iam::684120556289:role/roleAssumer" },', + ` "Principal": { "AWS": "${roleAssumerArn}" },`, ' "Action": "sts:AssumeRole",', ' "Condition": { "StringEquals": { "sts:ExternalId": "$EXTERNAL_ID" } }', ' }]', @@ -144,10 +203,10 @@ export const awsCloudShellScript = [ ' --query "Role.Arn" --output text)', '', 'aws iam attach-role-policy --role-name "$ROLE_NAME" \\', - ' --policy-arn arn:aws:iam::aws:policy/SecurityAudit', + ` --policy-arn ${securityAuditPolicyArn}`, '', 'aws iam attach-role-policy --role-name "$ROLE_NAME" \\', - ' --policy-arn arn:aws:iam::aws:policy/job-function/ViewOnlyAccess', + ` --policy-arn ${viewOnlyPolicyArn}`, '', 'aws iam put-role-policy --role-name "$ROLE_NAME" \\', ' --policy-name CompAI-CostExplorer \\', @@ -164,20 +223,32 @@ export const awsCloudShellScript = [ 'echo "============================================"', 'echo ""', 'echo "Paste these values into your Comp AI connection form."', -].join('\n'); + ')', + ].join('\n'); +} + +export const awsCloudShellScript = getAwsCloudShellScript(); /** * CloudShell setup script for the remediation IAM role. * Separate from the auditor role so the audit role stays read-only. */ -export const awsRemediationScript = `# Create Remediation Role for Auto-Fix +export function getAwsRemediationScript( + environment: AwsEnvironment = 'aws', +): string { + const roleAssumerArn = getAwsRoleAssumerArn(environment); + + return `# Create Remediation Role for Auto-Fix # Run this in AWS CloudShell after setting up the Auditor role. +( +set -euo pipefail + EXTERNAL_ID="YOUR_EXTERNAL_ID" ROLE_NAME="CompAI-Remediator" ROLE_ARN=$(aws iam create-role --role-name "$ROLE_NAME" --max-session-duration 3600 \\ - --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::684120556289:role/roleAssumer"},"Action":"sts:AssumeRole","Condition":{"StringEquals":{"sts:ExternalId":"'$EXTERNAL_ID'"}}}]}' \\ + --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"${roleAssumerArn}"},"Action":"sts:AssumeRole","Condition":{"StringEquals":{"sts:ExternalId":"'$EXTERNAL_ID'"}}}]}' \\ --query 'Role.Arn' --output text) # Storage Remediation: S3, DynamoDB, Redshift, Glue, Athena @@ -214,7 +285,11 @@ echo " Remediation Role ARN (paste this below):" echo "" echo " $ROLE_ARN" echo "" -echo "============================================"`; +echo "============================================" +)`; +} + +export const awsRemediationScript = getAwsRemediationScript(); /** * Setup instructions for AWS IAM Role From 7409f968efb0b91d09d53dac283cb7107fcb7e86 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Sun, 10 May 2026 01:52:04 -0400 Subject: [PATCH 2/5] refactor(cloud-tests): improve code formatting and organization - Refactored the CloudTestsSection component for better readability by adjusting line breaks and indentation. - Removed redundant imports and organized existing imports for clarity. - Enhanced filtering logic for findings to streamline the code. - Ensured consistent formatting across severity styles and service names for maintainability. --- .../cloud-security/ai-remediation.prompt.ts | 3 +- .../components/CloudTestsSection.tsx | 449 +++++++++--------- 2 files changed, 236 insertions(+), 216 deletions(-) diff --git a/apps/api/src/cloud-security/ai-remediation.prompt.ts b/apps/api/src/cloud-security/ai-remediation.prompt.ts index 7885e529bf..a471be0eda 100644 --- a/apps/api/src/cloud-security/ai-remediation.prompt.ts +++ b/apps/api/src/cloud-security/ai-remediation.prompt.ts @@ -114,7 +114,8 @@ function inferAwsRegion(finding: { resourceId: string; evidence: Record; }): string { - if (typeof finding.evidence.region === 'string') return finding.evidence.region; + if (typeof finding.evidence.region === 'string') + return finding.evidence.region; const arnMatch = finding.resourceId.match(/^arn:[^:]+:[^:]*:([^:]*):/); return arnMatch?.[1] || 'the execution region'; diff --git a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx index 7e70f73215..414469f937 100644 --- a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx +++ b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/CloudTestsSection.tsx @@ -1,6 +1,11 @@ 'use client'; import { useApi } from '@/hooks/use-api'; +import { + getAwsCloudShellUrl, + getAwsRemediationScript, + normalizeAwsEnvironment, +} from '@trycompai/integration-platform'; import { Badge } from '@trycompai/ui/badge'; import { Button } from '@trycompai/ui/button'; import { @@ -11,13 +16,11 @@ import { DialogTitle, } from '@trycompai/ui/dialog'; import { - AlertTriangle, Check, ChevronDown, ChevronRight, Copy, ExternalLink, - ListOrdered, Loader2, RefreshCw, Search, @@ -29,16 +32,11 @@ import { X, Zap, } from 'lucide-react'; -import { - getAwsCloudShellUrl, - getAwsRemediationScript, - normalizeAwsEnvironment, -} from '@trycompai/integration-platform'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { getActiveBatch } from '../actions/batch-fix'; -import { BatchRemediationDialog } from './BatchRemediationDialog'; import { AzureSetupGuide } from './AzureSetupGuide'; +import { BatchRemediationDialog } from './BatchRemediationDialog'; import { GcpSetupGuide } from './GcpSetupGuide'; import { RemediationDialog } from './RemediationDialog'; import { ScheduledScanPopover } from './ScheduledScanPopover'; @@ -71,78 +69,87 @@ interface CloudTestsSectionProps { } const SEVERITY_ORDER: Record = { - critical: 0, high: 1, medium: 2, low: 3, info: 4, + critical: 0, + high: 1, + medium: 2, + low: 3, + info: 4, }; const SEVERITY_STYLES: Record = { critical: { dot: 'bg-red-500', - badge: 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400', + badge: + 'border-red-200 bg-red-50 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400', }, high: { dot: 'bg-orange-500', - badge: 'border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-400', + badge: + 'border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-400', }, medium: { dot: 'bg-yellow-500', - badge: 'border-yellow-200 bg-yellow-50 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-400', + badge: + 'border-yellow-200 bg-yellow-50 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-400', }, low: { dot: 'bg-blue-500', - badge: 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-400', + badge: + 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-400', }, info: { dot: 'bg-gray-400', - badge: 'border-gray-200 bg-gray-50 text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400', + badge: + 'border-gray-200 bg-gray-50 text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400', }, }; const SERVICE_NAMES: Record = { 'security-hub': 'Security Hub', 'iam-analyzer': 'IAM Access Analyzer', - 'cloudtrail': 'CloudTrail', - 's3': 'S3 Bucket Security', + cloudtrail: 'CloudTrail', + s3: 'S3 Bucket Security', 'ec2-vpc': 'EC2 & VPC Security', - 'rds': 'RDS Security', - 'kms': 'KMS', - 'cloudwatch': 'CloudWatch', - 'config': 'AWS Config', - 'guardduty': 'GuardDuty', + rds: 'RDS Security', + kms: 'KMS', + cloudwatch: 'CloudWatch', + config: 'AWS Config', + guardduty: 'GuardDuty', 'secrets-manager': 'Secrets Manager', - 'waf': 'WAF', - 'elb': 'ELB / ALB', - 'acm': 'ACM', - 'backup': 'AWS Backup', - 'inspector': 'Inspector', + waf: 'WAF', + elb: 'ELB / ALB', + acm: 'ACM', + backup: 'AWS Backup', + inspector: 'Inspector', 'ecs-eks': 'ECS & EKS', - 'lambda': 'Lambda', - 'dynamodb': 'DynamoDB', + lambda: 'Lambda', + dynamodb: 'DynamoDB', 'sns-sqs': 'SNS & SQS', - 'ecr': 'ECR', - 'opensearch': 'OpenSearch', - 'redshift': 'Redshift', - 'macie': 'Macie', - 'route53': 'Route 53', + ecr: 'ECR', + opensearch: 'OpenSearch', + redshift: 'Redshift', + macie: 'Macie', + route53: 'Route 53', 'api-gateway': 'API Gateway', - 'cloudfront': 'CloudFront', - 'cognito': 'Cognito', - 'elasticache': 'ElastiCache', - 'efs': 'EFS', - 'msk': 'MSK', - 'sagemaker': 'SageMaker', + cloudfront: 'CloudFront', + cognito: 'Cognito', + elasticache: 'ElastiCache', + efs: 'EFS', + msk: 'MSK', + sagemaker: 'SageMaker', 'systems-manager': 'Systems Manager', - 'codebuild': 'CodeBuild', + codebuild: 'CodeBuild', 'network-firewall': 'Network Firewall', - 'shield': 'Shield', - 'kinesis': 'Kinesis', - 'glue': 'Glue', - 'athena': 'Athena', - 'emr': 'EMR', + shield: 'Shield', + kinesis: 'Kinesis', + glue: 'Glue', + athena: 'Athena', + emr: 'EMR', 'step-functions': 'Step Functions', - 'eventbridge': 'EventBridge', + eventbridge: 'EventBridge', 'transfer-family': 'Transfer Family', 'elastic-beanstalk': 'Elastic Beanstalk', - 'appflow': 'AppFlow', + appflow: 'AppFlow', }; interface ServiceGroup { @@ -178,8 +185,7 @@ export function CloudTestsSection({ const [severityFilter, setSeverityFilter] = useState(null); const [projectFilter, setProjectFilter] = useState(null); const [searchQuery, setSearchQuery] = useState(''); - const [capabilities, setCapabilities] = - useState(null); + const [capabilities, setCapabilities] = useState(null); const [capabilitiesLoaded, setCapabilitiesLoaded] = useState(false); const [remediationTarget, setRemediationTarget] = useState<{ connectionId: string; @@ -206,7 +212,11 @@ export function CloudTestsSection({ // Load remediation capabilities for the selected connection useEffect(() => { - if (!connectionId || (providerSlug !== 'aws' && providerSlug !== 'gcp' && providerSlug !== 'azure')) return; + if ( + !connectionId || + (providerSlug !== 'aws' && providerSlug !== 'gcp' && providerSlug !== 'azure') + ) + return; const loadCapabilities = async () => { const resp = await api.get( @@ -223,7 +233,11 @@ export function CloudTestsSection({ // Check for active batch once on mount (separate from capabilities to avoid re-runs) useEffect(() => { - if (!connectionId || (providerSlug !== 'aws' && providerSlug !== 'gcp' && providerSlug !== 'azure')) return; + if ( + !connectionId || + (providerSlug !== 'aws' && providerSlug !== 'gcp' && providerSlug !== 'azure') + ) + return; let cancelled = false; getActiveBatch(connectionId).then((batch) => { @@ -234,7 +248,9 @@ export function CloudTestsSection({ } }); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [connectionId]); @@ -249,17 +265,11 @@ export function CloudTestsSection({ const findings = useMemo(() => { return allFindings - .filter( - (f) => - f.providerSlug === providerSlug || f.connectionId === connectionId, - ) - .filter( - (f) => !projectFilter || f.projectDisplayName === projectFilter, - ) + .filter((f) => f.providerSlug === providerSlug || f.connectionId === connectionId) + .filter((f) => !projectFilter || f.projectDisplayName === projectFilter) .sort( (a, b) => - (SEVERITY_ORDER[a.severity ?? 'info'] ?? 5) - - (SEVERITY_ORDER[b.severity ?? 'info'] ?? 5), + (SEVERITY_ORDER[a.severity ?? 'info'] ?? 5) - (SEVERITY_ORDER[b.severity ?? 'info'] ?? 5), ); }, [allFindings, providerSlug, connectionId, projectFilter]); @@ -277,12 +287,8 @@ export function CloudTestsSection({ return [...names].sort((a, b) => a.localeCompare(b)); }, [allFindings, providerSlug, connectionId]); - const failedFindings = findings.filter( - (f) => f.status === 'failed' || f.status === 'FAILED', - ); - const passedFindings = findings.filter( - (f) => f.status === 'passed' || f.status === 'success', - ); + const failedFindings = findings.filter((f) => f.status === 'failed' || f.status === 'FAILED'); + const passedFindings = findings.filter((f) => f.status === 'passed' || f.status === 'success'); // Group findings by serviceId const serviceGroups = useMemo(() => { @@ -300,12 +306,8 @@ export function CloudTestsSection({ const serviceName = SERVICE_NAMES[serviceId] ?? serviceId; const serviceMatches = q ? serviceName.toLowerCase().includes(q) : true; - const failed = groupFindings.filter( - (f) => f.status === 'failed' || f.status === 'FAILED', - ); - const passed = groupFindings.filter( - (f) => f.status === 'passed' || f.status === 'success', - ); + const failed = groupFindings.filter((f) => f.status === 'failed' || f.status === 'FAILED'); + const passed = groupFindings.filter((f) => f.status === 'passed' || f.status === 'success'); let filteredFailed = severityFilter ? failed.filter((f) => f.severity?.toLowerCase() === severityFilter) @@ -336,13 +338,22 @@ export function CloudTestsSection({ }, [findings, severityFilter, searchQuery]); // Split into baseline (security fundamentals) vs service-specific - const BASELINE_SERVICE_IDS = new Set(['cloudtrail', 'config', 'guardduty', 'iam', 'cloudwatch', 'kms']); - const baselineGroups = providerSlug === 'aws' - ? serviceGroups.filter((g) => BASELINE_SERVICE_IDS.has(g.serviceId)) - : []; - const regularGroups = providerSlug === 'aws' - ? serviceGroups.filter((g) => !BASELINE_SERVICE_IDS.has(g.serviceId)) - : serviceGroups; + const BASELINE_SERVICE_IDS = new Set([ + 'cloudtrail', + 'config', + 'guardduty', + 'iam', + 'cloudwatch', + 'kms', + ]); + const baselineGroups = + providerSlug === 'aws' + ? serviceGroups.filter((g) => BASELINE_SERVICE_IDS.has(g.serviceId)) + : []; + const regularGroups = + providerSlug === 'aws' + ? serviceGroups.filter((g) => !BASELINE_SERVICE_IDS.has(g.serviceId)) + : serviceGroups; const severityCounts = useMemo(() => { const counts: Record = {}; @@ -364,14 +375,12 @@ export function CloudTestsSection({ success?: boolean; message?: string; errorCode?: string; - }>( - `/v1/cloud-security/scan/${connectionId}`, - {}, - ); + }>(`/v1/cloud-security/scan/${connectionId}`, {}); if (response.error) { const data = response.data as { message?: string; errorCode?: string } | undefined; const errorCode = data?.errorCode; - const message = data?.message ?? (typeof response.error === 'string' ? response.error : 'Scan failed'); + const message = + data?.message ?? (typeof response.error === 'string' ? response.error : 'Scan failed'); // GCP setup errors get persistent inline message if (errorCode === 'SCC_NOT_ACTIVATED' || errorCode === 'GCP_ORG_MISSING') { setScanError({ message, errorCode }); @@ -386,9 +395,7 @@ export function CloudTestsSection({ const elapsed = Math.round((Date.now() - startTime) / 1000); toast.success(`Scan completed in ${elapsed}s!`); } catch (err) { - toast.error( - `Scan failed: ${err instanceof Error ? err.message : 'Unknown error'}`, - ); + toast.error(`Scan failed: ${err instanceof Error ? err.message : 'Unknown error'}`); } finally { setIsScanning(false); } @@ -450,9 +457,7 @@ export function CloudTestsSection({

Batch fix in progress

-

- Click to view progress -

+

Click to view progress

@@ -464,7 +469,9 @@ export function CloudTestsSection({

Scanning...

-

Verifying your cloud security posture. This may take a moment.

+

+ Verifying your cloud security posture. This may take a moment. +

)} @@ -479,12 +486,7 @@ export function CloudTestsSection({
-
{/* Selected projects indicator (GCP) */} - {providerSlug === 'gcp' && (() => { - const ids = Array.isArray(variables?.project_ids) - ? (variables.project_ids as string[]) - : []; - const savedNames = (variables?.project_names ?? {}) as Record; - return ids.length > 0 ? ( -
- ) : ( -
-
- - + {providerSlug === 'gcp' && + (() => { + const ids = Array.isArray(variables?.project_ids) + ? (variables.project_ids as string[]) + : []; + const savedNames = (variables?.project_names ?? {}) as Record; + return ids.length > 0 ? ( +
+ + - No projects selected — select projects to scope your scan. + + {ids.length} project{ids.length > 1 ? 's' : ''}: + +
+ {ids.map((id: string) => { + const name = savedNames[id]; + return ( + + {name ?? id} + {name && {id}} + + ); + })} +
+ + Change +
- - Select projects - -
- ); - })()} + ) : ( +
+
+ + + + + No projects selected — select projects to scope your scan. + +
+ + Select projects + +
+ ); + })()} {/* Stats row */}
@@ -586,9 +616,7 @@ export function CloudTestsSection({
- {saveError && ( -

{saveError}

- )} + {saveError &&

{saveError}

}

- The remediation role is separate from your audit role — your audit - role stays read-only. + The remediation role is separate from your audit role — your audit role stays read-only.

@@ -1299,7 +1324,8 @@ function FindingRow({ onClick={(e) => { // Don't toggle if user clicked a button or interactive element const target = e.target as HTMLElement; - if (target.closest('button') || target.closest('a') || target.tagName === 'BUTTON') return; + if (target.closest('button') || target.closest('a') || target.tagName === 'BUTTON') + return; onToggle(); }} onKeyDown={(e) => { @@ -1310,16 +1336,10 @@ function FindingRow({ }} > - {expanded ? ( - - ) : ( - - )} + {expanded ? : } - - {finding.title ?? 'Untitled finding'} - + {finding.title ?? 'Untitled finding'} {finding.projectDisplayName && ( {finding.projectDisplayName} @@ -1328,23 +1348,22 @@ function FindingRow({ e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}> {renderFixButton()} - + {severity}
{expanded && (
{finding.description && ( -

- {finding.description} -

+

{finding.description}

)} {finding.remediation && (

Remediation

-

- {finding.remediation} -

+

{finding.remediation}

)}
From 44d7cbdf90d9f4f6e1ff0f607ee5e87d18dd4bc8 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Sun, 10 May 2026 02:03:31 -0400 Subject: [PATCH 3/5] refactor(aws-account-settings): remove console log and simplify credential update logic - Removed unnecessary console log from AwsAccountSettingsBody component. - Simplified the updateCredential function in EmptyStateOnboarding and CloudSetup components by removing redundant logic for awsType, ensuring cleaner state management. --- .../[slug]/components/EmptyStateOnboarding.tsx | 12 ++++++------ .../[slug]/components/aws-account-settings-body.tsx | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx index 38e9b7081a..18091edcf2 100644 --- a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx +++ b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/EmptyStateOnboarding.tsx @@ -361,11 +361,7 @@ function CredentialSetup({ const hasConfigurableFields = fields.length > 0; const updateCredential = (fieldId: string, value: string | string[]) => { - setCredentials((prev) => ({ - ...prev, - [fieldId]: value, - ...(fieldId === 'awsType' ? { regions: [] } : {}), - })); + setCredentials((prev) => ({ ...prev, [fieldId]: value })); if (errors[fieldId]) { setErrors((prev) => { const next = { ...prev }; @@ -508,7 +504,11 @@ function CloudSetup({ ); const updateCredential = (fieldId: string, value: string | string[]) => { - setCredentials((prev) => ({ ...prev, [fieldId]: value })); + setCredentials((prev) => ({ + ...prev, + [fieldId]: value, + ...(fieldId === 'awsType' ? { regions: [] } : {}), + })); if (errors[fieldId]) { setErrors((prev) => { const next = { ...prev }; diff --git a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/aws-account-settings-body.tsx b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/aws-account-settings-body.tsx index ea1b15fbf4..d05deceb6c 100644 --- a/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/aws-account-settings-body.tsx +++ b/apps/app/src/app/(app)/[orgId]/integrations/[slug]/components/aws-account-settings-body.tsx @@ -82,8 +82,6 @@ export function AwsAccountSettingsBody({ : [], ); setAwsType(nextAwsType); - - console.log('awsType', nextAwsType, metadata); }, [ connection, metadata.roleArn, From 9e37a758c550ec2e1402b262d426d772b7184810 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 14:27:34 +0100 Subject: [PATCH 4/5] Remove hero images from introduction.mdx (#2810) Mintlify-Source: dashboard-editor Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> --- packages/docs/introduction.mdx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/docs/introduction.mdx b/packages/docs/introduction.mdx index 09825ca20e..64c49cd4d3 100644 --- a/packages/docs/introduction.mdx +++ b/packages/docs/introduction.mdx @@ -2,20 +2,6 @@ title: "Welcome to Comp AI" --- -Hero Light - -Hero Dark - ## Quick Links Resource links for Comp AI features @@ -24,6 +10,7 @@ Resource links for Comp AI features Connect to cloud providers such as AWS, GCP and Azure + Monitor your employee devices with ease with the Comp AI device agent From 1275f84db668d50dee59ab2bcf979ec2576de63e Mon Sep 17 00:00:00 2001 From: claudio Date: Sun, 10 May 2026 22:35:35 +0100 Subject: [PATCH 5/5] Improve public API docs SEO and Mintlify metadata (#2811) * fix(docs): improve questionnaire API SEO metadata * docs: add Mintlify API overview and metadata layer * docs(api): broaden public API SEO metadata * docs(api): harden public OpenAPI SEO surface --- apps/api/packages/docs/openapi.json | 11109 ----------- apps/api/src/gen-openapi.spec.ts | 51 +- apps/api/src/main.ts | 11 +- apps/api/src/openapi-docs.spec.ts | 97 +- apps/api/src/openapi/operation-metadata.ts | 271 + apps/api/src/openapi/public-docs-metadata.ts | 237 + apps/api/src/openapi/public-docs-quality.ts | 120 + .../api/src/openapi/questionnaire-metadata.ts | 97 + apps/api/src/openapi/schema-pruning.ts | 126 + apps/api/src/openapi/seo-text.ts | 80 + apps/api/src/openapi/tag-metadata.ts | 191 + .../src/openapi/trust-operation-metadata.ts | 136 + apps/api/src/openapi/types.ts | 30 + .../openapi/workflow-operation-metadata.ts | 147 + packages/docs/api-reference/overview.mdx | 96 + packages/docs/automated-evidence.mdx | 20 +- packages/docs/device-agent.mdx | 63 +- packages/docs/docs.json | 86 +- packages/docs/introduction.mdx | 40 +- packages/docs/openapi.json | 15522 +++++++--------- packages/docs/penetration-tests.mdx | 22 +- .../security-questionnaire-trust-center.mdx | 511 - packages/docs/security-questionnaire.mdx | 34 +- packages/docs/trust-access.mdx | 9 +- 24 files changed, 8855 insertions(+), 20251 deletions(-) delete mode 100644 apps/api/packages/docs/openapi.json create mode 100644 apps/api/src/openapi/operation-metadata.ts create mode 100644 apps/api/src/openapi/public-docs-metadata.ts create mode 100644 apps/api/src/openapi/public-docs-quality.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/trust-operation-metadata.ts create mode 100644 apps/api/src/openapi/types.ts create mode 100644 apps/api/src/openapi/workflow-operation-metadata.ts create mode 100644 packages/docs/api-reference/overview.mdx delete mode 100644 packages/docs/security-questionnaire-trust-center.mdx diff --git a/apps/api/packages/docs/openapi.json b/apps/api/packages/docs/openapi.json deleted file mode 100644 index ecbfb89358..0000000000 --- a/apps/api/packages/docs/openapi.json +++ /dev/null @@ -1,11109 +0,0 @@ -{ - "openapi": "3.0.0", - "paths": { - "/v1/organization": { - "get": { - "description": "Returns detailed information about the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "OrganizationController_getOrganization_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Organization information retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The organization ID", - "example": "org_abc123def456" - }, - "name": { - "type": "string", - "description": "Organization name", - "example": "Acme Corporation" - }, - "slug": { - "type": "string", - "description": "Organization slug", - "example": "acme-corp" - }, - "logo": { - "type": "string", - "nullable": true, - "description": "Organization logo URL", - "example": "https://example.com/logo.png" - }, - "metadata": { - "type": "string", - "nullable": true, - "description": "Additional metadata in JSON format", - "example": "{\"theme\": \"dark\", \"preferences\": {}}" - }, - "website": { - "type": "string", - "nullable": true, - "description": "Organization website URL", - "example": "https://acme-corp.com" - }, - "onboardingCompleted": { - "type": "boolean", - "description": "Whether onboarding is completed", - "example": true - }, - "hasAccess": { - "type": "boolean", - "description": "Whether organization has access to the platform", - "example": true - }, - "fleetDmLabelId": { - "type": "integer", - "nullable": true, - "description": "FleetDM label ID for device management", - "example": 123 - }, - "isFleetSetupCompleted": { - "type": "boolean", - "description": "Whether FleetDM setup is completed", - "example": false - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the organization was created" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get organization information", - "tags": [ - "Organization" - ] - }, - "patch": { - "description": "Partially updates the authenticated organization. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "OrganizationController_updateOrganization_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Organization update data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Organization name", - "example": "New Acme Corporation" - }, - "slug": { - "type": "string", - "description": "Organization slug", - "example": "new-acme-corp" - }, - "logo": { - "type": "string", - "description": "Organization logo URL", - "example": "https://example.com/logo.png" - }, - "metadata": { - "type": "string", - "description": "Additional metadata in JSON format", - "example": "{\"theme\": \"dark\", \"preferences\": {}}" - }, - "website": { - "type": "string", - "description": "Organization website URL", - "example": "https://acme-corp.com" - }, - "onboardingCompleted": { - "type": "boolean", - "description": "Whether onboarding is completed", - "example": true - }, - "hasAccess": { - "type": "boolean", - "description": "Whether organization has access to the platform", - "example": true - }, - "fleetDmLabelId": { - "type": "integer", - "description": "FleetDM label ID for device management", - "example": 123 - }, - "isFleetSetupCompleted": { - "type": "boolean", - "description": "Whether FleetDM setup is completed", - "example": false - } - }, - "additionalProperties": false - } - } - } - }, - "responses": { - "200": { - "description": "Organization updated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The organization ID", - "example": "org_abc123def456" - }, - "name": { - "type": "string", - "description": "Organization name", - "example": "New Acme Corporation" - }, - "slug": { - "type": "string", - "description": "Organization slug", - "example": "new-acme-corp" - }, - "logo": { - "type": "string", - "nullable": true, - "description": "Organization logo URL", - "example": "https://example.com/logo.png" - }, - "metadata": { - "type": "string", - "nullable": true, - "description": "Additional metadata in JSON format", - "example": "{\"theme\": \"dark\", \"preferences\": {}}" - }, - "website": { - "type": "string", - "nullable": true, - "description": "Organization website URL", - "example": "https://acme-corp.com" - }, - "onboardingCompleted": { - "type": "boolean", - "description": "Whether onboarding is completed", - "example": true - }, - "hasAccess": { - "type": "boolean", - "description": "Whether organization has access to the platform", - "example": true - }, - "fleetDmLabelId": { - "type": "integer", - "nullable": true, - "description": "FleetDM label ID for device management", - "example": 123 - }, - "isFleetSetupCompleted": { - "type": "boolean", - "description": "Whether FleetDM setup is completed", - "example": false - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the organization was created" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - } - } - } - } - } - }, - "400": { - "description": "Bad Request - Invalid update data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid slug format" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update organization", - "tags": [ - "Organization" - ] - }, - "delete": { - "description": "Permanently deletes the authenticated organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "OrganizationController_deleteOrganization_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Organization deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "description": "Indicates successful deletion", - "example": true - }, - "deletedOrganization": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The deleted organization ID", - "example": "org_abc123def456" - }, - "name": { - "type": "string", - "description": "The deleted organization name", - "example": "Acme Corporation" - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete organization", - "tags": [ - "Organization" - ] - } - }, - "/v1/people": { - "get": { - "description": "Returns all members for the authenticated organization with their user information. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_getAllPeople_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "People retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PeopleResponseDto" - } - }, - "count": { - "type": "number", - "description": "Total number of people", - "example": 25 - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "User ID", - "example": "usr_abc123def456" - }, - "email": { - "type": "string", - "description": "User email", - "example": "user@company.com" - } - } - } - } - }, - "example": { - "data": [ - { - "id": "mem_abc123def456", - "organizationId": "org_abc123def456", - "userId": "usr_abc123def456", - "role": "admin", - "createdAt": "2024-01-01T00:00:00Z", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123, - "user": { - "id": "usr_abc123def456", - "name": "John Doe", - "email": "john.doe@company.com", - "emailVerified": true, - "image": "https://example.com/avatar.jpg", - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-15T00:00:00Z", - "lastLogin": "2024-01-15T12:00:00Z" - } - } - ], - "count": 1, - "authType": "api-key", - "authenticatedUser": { - "id": "usr_abc123def456", - "email": "user@company.com" - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Failed to retrieve members" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all people", - "tags": [ - "People" - ] - }, - "post": { - "description": "Adds a new member to the authenticated organization. The user must already exist in the system. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_createMember_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Member creation data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreatePeopleDto" - } - } - } - }, - "responses": { - "201": { - "description": "Member created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PeopleResponseDto" - }, - "example": { - "id": "mem_abc123def456", - "organizationId": "org_abc123def456", - "userId": "usr_abc123def456", - "role": "admin", - "createdAt": "2024-01-01T00:00:00Z", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123, - "user": { - "id": "usr_abc123def456", - "name": "John Doe", - "email": "john.doe@company.com", - "emailVerified": true, - "image": "https://example.com/avatar.jpg", - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-15T00:00:00Z", - "lastLogin": "2024-01-15T12:00:00Z" - } - } - } - } - }, - "400": { - "description": "Bad Request - Invalid member data or user already exists", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "User user@example.com is already a member of this organization" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization or user not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "User with ID usr_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Failed to create member" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new member", - "tags": [ - "People" - ] - } - }, - "/v1/people/bulk": { - "post": { - "description": "Bulk adds multiple members to the authenticated organization. Each member must have a valid user ID that exists in the system. Members who already exist in the organization or have invalid data will be skipped with error details returned. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_bulkCreateMembers_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Bulk member creation data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BulkCreatePeopleDto" - } - } - } - }, - "responses": { - "201": { - "description": "Bulk member creation completed", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "created": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PeopleResponseDto" - }, - "description": "Successfully created members" - }, - "errors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "index": { - "type": "number", - "description": "Index in the original array where the error occurred", - "example": 2 - }, - "userId": { - "type": "string", - "description": "User ID that failed to be added", - "example": "usr_abc123def456" - }, - "error": { - "type": "string", - "description": "Error message explaining why the member could not be created", - "example": "User user@example.com is already a member of this organization" - } - } - }, - "description": "Members that failed to be created with error details" - }, - "summary": { - "type": "object", - "properties": { - "total": { - "type": "number", - "description": "Total number of members in the request", - "example": 5 - }, - "successful": { - "type": "number", - "description": "Number of members successfully created", - "example": 3 - }, - "failed": { - "type": "number", - "description": "Number of members that failed to be created", - "example": 2 - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "User ID", - "example": "usr_abc123def456" - }, - "email": { - "type": "string", - "description": "User email", - "example": "user@company.com" - } - } - } - } - }, - "example": { - "created": [ - { - "id": "mem_abc123def456", - "organizationId": "org_abc123def456", - "userId": "usr_abc123def456", - "role": "member", - "createdAt": "2024-01-01T00:00:00Z", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123, - "user": { - "id": "usr_abc123def456", - "name": "John Doe", - "email": "john.doe@company.com", - "emailVerified": true, - "image": "https://example.com/avatar.jpg", - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-15T00:00:00Z", - "lastLogin": "2024-01-15T12:00:00Z" - } - } - ], - "errors": [ - { - "index": 2, - "userId": "usr_xyz789abc123", - "error": "User user2@example.com is already a member of this organization" - } - ], - "summary": { - "total": 2, - "successful": 1, - "failed": 1 - }, - "authType": "api-key", - "authenticatedUser": { - "id": "usr_admin123", - "email": "admin@company.com" - } - } - } - } - }, - "400": { - "description": "Bad Request - Invalid bulk data or validation errors", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Members array cannot be empty" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Bulk creation failed" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Add multiple members to organization", - "tags": [ - "People" - ] - } - }, - "/v1/people/{id}": { - "get": { - "description": "Returns a specific member by ID for the authenticated organization with their user information. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_getPersonById_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Member ID", - "schema": { - "example": "mem_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Person retrieved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PeopleResponseDto" - }, - "example": { - "id": "mem_abc123def456", - "organizationId": "org_abc123def456", - "userId": "usr_abc123def456", - "role": "admin", - "createdAt": "2024-01-01T00:00:00Z", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123, - "user": { - "id": "usr_abc123def456", - "name": "John Doe", - "email": "john.doe@company.com", - "emailVerified": true, - "image": "https://example.com/avatar.jpg", - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-15T00:00:00Z", - "lastLogin": "2024-01-15T12:00:00Z" - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization or member not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Member with ID mem_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get person by ID", - "tags": [ - "People" - ] - }, - "patch": { - "description": "Partially updates a member. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_updateMember_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Member ID", - "schema": { - "example": "mem_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Member update data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdatePeopleDto" - } - } - } - }, - "responses": { - "200": { - "description": "Member updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PeopleResponseDto" - }, - "example": { - "id": "mem_abc123def456", - "organizationId": "org_abc123def456", - "userId": "usr_abc123def456", - "role": "member", - "createdAt": "2024-01-01T00:00:00Z", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123, - "user": { - "id": "usr_abc123def456", - "name": "John Doe", - "email": "john.doe@company.com", - "emailVerified": true, - "image": "https://example.com/avatar.jpg", - "createdAt": "2024-01-01T00:00:00Z", - "updatedAt": "2024-01-15T00:00:00Z", - "lastLogin": "2024-01-15T12:00:00Z" - } - } - } - } - }, - "400": { - "description": "Bad Request - Invalid update data or user conflict", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "User user@example.com is already a member of this organization" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization, member, or user not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Member with ID mem_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update member", - "tags": [ - "People" - ] - }, - "delete": { - "description": "Permanently removes a member from the organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PeopleController_deleteMember_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Member ID", - "schema": { - "example": "mem_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Member deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "description": "Indicates successful deletion", - "example": true - }, - "deletedMember": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The deleted member ID", - "example": "mem_abc123def456" - }, - "name": { - "type": "string", - "description": "The deleted member name", - "example": "John Doe" - }, - "email": { - "type": "string", - "description": "The deleted member email", - "example": "john.doe@company.com" - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization or member not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Member with ID mem_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Failed to delete member" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete member", - "tags": [ - "People" - ] - } - }, - "/v1/risks": { - "get": { - "description": "Returns all risks for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "RisksController_getAllRisks_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Risks retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Risk ID", - "example": "rsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Risk description", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "enum": [ - "customer", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "status": { - "type": "string", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "example": "open" - }, - "likelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "impact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "major" - }, - "treatmentStrategy": { - "type": "string", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "example": "mitigate" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was last updated" - } - } - } - }, - "count": { - "type": "number", - "description": "Total number of risks", - "example": 15 - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all risks", - "tags": [ - "Risks" - ] - }, - "post": { - "description": "Creates a new risk for the authenticated organization. All required fields must be provided. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "RisksController_createRisk_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Risk creation data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateRiskDto" - } - } - } - }, - "responses": { - "201": { - "description": "Risk created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Risk ID", - "example": "rsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Risk description", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "enum": [ - "customer", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "department": { - "type": "string", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "nullable": true, - "example": "it" - }, - "status": { - "type": "string", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "example": "open" - }, - "likelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "impact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "major" - }, - "residualLikelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "treatmentStrategyDescription": { - "type": "string", - "nullable": true, - "example": "Implement multi-factor authentication and strengthen password requirements" - }, - "treatmentStrategy": { - "type": "string", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "example": "mitigate" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "title should not be empty", - "description should not be empty", - "category must be a valid enum value" - ] - }, - "error": { - "type": "string", - "example": "Bad Request" - }, - "statusCode": { - "type": "number", - "example": 400 - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new risk", - "tags": [ - "Risks" - ] - } - }, - "/v1/risks/{id}": { - "get": { - "description": "Returns a specific risk by ID for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "RisksController_getRiskById_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Risk ID", - "schema": { - "example": "rsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Risk retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Risk ID", - "example": "rsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Risk description", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "enum": [ - "customer", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "department": { - "type": "string", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "nullable": true, - "example": "it" - }, - "status": { - "type": "string", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "example": "open" - }, - "likelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "impact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "major" - }, - "residualLikelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "treatmentStrategyDescription": { - "type": "string", - "nullable": true, - "example": "Implement multi-factor authentication and strengthen password requirements" - }, - "treatmentStrategy": { - "type": "string", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "example": "mitigate" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Risk not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Risk with ID rsk_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get risk by ID", - "tags": [ - "Risks" - ] - }, - "patch": { - "description": "Partially updates a risk. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "RisksController_updateRisk_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Risk ID", - "schema": { - "example": "rsk_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Risk update data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateRiskDto" - } - } - } - }, - "responses": { - "200": { - "description": "Risk updated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Risk ID", - "example": "rsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Risk description", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "enum": [ - "customer", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "department": { - "type": "string", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "nullable": true, - "example": "it" - }, - "status": { - "type": "string", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "example": "open" - }, - "likelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "impact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "major" - }, - "residualLikelihood": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "treatmentStrategyDescription": { - "type": "string", - "nullable": true, - "example": "Implement multi-factor authentication and strengthen password requirements" - }, - "treatmentStrategy": { - "type": "string", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "example": "mitigate" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the risk was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "title should not be empty", - "category must be a valid enum value", - "status must be a valid enum value" - ] - }, - "error": { - "type": "string", - "example": "Bad Request" - }, - "statusCode": { - "type": "number", - "example": 400 - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Risk not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Risk with ID rsk_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update risk", - "tags": [ - "Risks" - ] - }, - "delete": { - "description": "Permanently removes a risk from the organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "RisksController_deleteRisk_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Risk ID", - "schema": { - "example": "rsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Risk deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Risk deleted successfully" - }, - "deletedRisk": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Deleted risk ID", - "example": "rsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Deleted risk title", - "example": "Data breach vulnerability in user authentication system" - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Risk not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Risk with ID rsk_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete risk", - "tags": [ - "Risks" - ] - } - }, - "/v1/vendors": { - "get": { - "description": "Returns all vendors for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "VendorsController_getAllVendors_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Vendors retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Vendor ID", - "example": "vnd_abc123def456" - }, - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Vendor description", - "example": "Cloud infrastructure provider offering AWS-like services" - }, - "category": { - "type": "string", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "example": "cloud" - }, - "status": { - "type": "string", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "example": "not_assessed" - }, - "inherentProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "website": { - "type": "string", - "nullable": true, - "example": "https://www.cloudtechsolutions.com" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was last updated" - } - } - } - }, - "count": { - "type": "number", - "description": "Total number of vendors", - "example": 12 - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all vendors", - "tags": [ - "Vendors" - ] - }, - "post": { - "description": "Creates a new vendor for the authenticated organization. All required fields must be provided. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "VendorsController_createVendor_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Vendor creation data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateVendorDto" - } - } - } - }, - "responses": { - "201": { - "description": "Vendor created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Vendor ID", - "example": "vnd_abc123def456" - }, - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Vendor description", - "example": "Cloud infrastructure provider offering AWS-like services including compute, storage, and networking solutions for enterprise customers." - }, - "category": { - "type": "string", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "example": "cloud" - }, - "status": { - "type": "string", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "example": "not_assessed" - }, - "inherentProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "website": { - "type": "string", - "nullable": true, - "example": "https://www.cloudtechsolutions.com" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "name should not be empty", - "description should not be empty", - "category must be a valid enum value", - "website must be a URL address" - ] - }, - "error": { - "type": "string", - "example": "Bad Request" - }, - "statusCode": { - "type": "number", - "example": 400 - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new vendor", - "tags": [ - "Vendors" - ] - } - }, - "/v1/vendors/{id}": { - "get": { - "description": "Returns a specific vendor by ID for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "VendorsController_getVendorById_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Vendor ID", - "schema": { - "example": "vnd_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Vendor retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Vendor ID", - "example": "vnd_abc123def456" - }, - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Vendor description", - "example": "Cloud infrastructure provider offering AWS-like services including compute, storage, and networking solutions for enterprise customers." - }, - "category": { - "type": "string", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "example": "cloud" - }, - "status": { - "type": "string", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "example": "not_assessed" - }, - "inherentProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "website": { - "type": "string", - "nullable": true, - "example": "https://www.cloudtechsolutions.com" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Vendor not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Vendor with ID vnd_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get vendor by ID", - "tags": [ - "Vendors" - ] - }, - "patch": { - "description": "Partially updates a vendor. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "VendorsController_updateVendor_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Vendor ID", - "schema": { - "example": "vnd_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Vendor update data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateVendorDto" - } - } - } - }, - "responses": { - "200": { - "description": "Vendor updated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Vendor ID", - "example": "vnd_abc123def456" - }, - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Vendor description", - "example": "Cloud infrastructure provider offering AWS-like services including compute, storage, and networking solutions for enterprise customers." - }, - "category": { - "type": "string", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "example": "cloud" - }, - "status": { - "type": "string", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "example": "assessed" - }, - "inherentProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "example": "minor" - }, - "website": { - "type": "string", - "nullable": true, - "example": "https://www.cloudtechsolutions.com" - }, - "organizationId": { - "type": "string", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "nullable": true, - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - }, - "createdAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was created" - }, - "updatedAt": { - "type": "string", - "format": "date-time", - "description": "When the vendor was last updated" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "name should not be empty", - "category must be a valid enum value", - "status must be a valid enum value", - "website must be a URL address" - ] - }, - "error": { - "type": "string", - "example": "Bad Request" - }, - "statusCode": { - "type": "number", - "example": 400 - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Vendor not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Vendor with ID vnd_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update vendor", - "tags": [ - "Vendors" - ] - }, - "delete": { - "description": "Permanently removes a vendor from the organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "VendorsController_deleteVendor_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Vendor ID", - "schema": { - "example": "vnd_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Vendor deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Vendor deleted successfully" - }, - "deletedVendor": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Deleted vendor ID", - "example": "vnd_abc123def456" - }, - "name": { - "type": "string", - "description": "Deleted vendor name", - "example": "CloudTech Solutions Inc." - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "description": "User information (only for session auth)", - "properties": { - "id": { - "type": "string", - "example": "usr_def456ghi789" - }, - "email": { - "type": "string", - "example": "user@example.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Vendor not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Vendor with ID vnd_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal server error" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete vendor", - "tags": [ - "Vendors" - ] - } - }, - "/v1/context": { - "get": { - "description": "Returns all context entries for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "ContextController_getAllContext_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Context entries retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "organizationId": { - "type": "string" - }, - "question": { - "type": "string" - }, - "answer": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - } - } - } - }, - "count": { - "type": "number" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ] - } - } - }, - "example": { - "data": [ - { - "id": "ctx_abc123def456", - "organizationId": "org_xyz789uvw012", - "question": "How do we handle user authentication in our application?", - "answer": "We use a hybrid authentication system supporting both API keys and session-based authentication.", - "tags": [ - "authentication", - "security", - "api", - "sessions" - ], - "createdAt": "2024-01-15T10:30:00.000Z", - "updatedAt": "2024-01-15T14:20:00.000Z" - }, - { - "id": "ctx_ghi789jkl012", - "organizationId": "org_xyz789uvw012", - "question": "What database do we use and why?", - "answer": "We use PostgreSQL as our primary database with Prisma as the ORM.", - "tags": [ - "database", - "postgresql", - "prisma", - "architecture" - ], - "createdAt": "2024-01-14T09:15:00.000Z", - "updatedAt": "2024-01-14T09:15:00.000Z" - } - ], - "count": 2, - "authType": "apikey" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 401 - } - } - }, - "example": { - "message": "Unauthorized", - "statusCode": 401 - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 404 - } - } - }, - "example": { - "message": "Organization not found", - "statusCode": 404 - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 500 - } - } - }, - "example": { - "message": "Internal server error", - "statusCode": 500 - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all context entries", - "tags": [ - "Context" - ] - }, - "post": { - "description": "Creates a new context entry for the authenticated organization. All required fields must be provided. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "ContextController_createContext_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Context entry data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateContextDto" - }, - "examples": { - "Authentication Context": { - "value": { - "question": "How do we handle user authentication in our application?", - "answer": "We use a hybrid authentication system supporting both API keys and session-based authentication. API keys are used for programmatic access while sessions are used for web interface interactions.", - "tags": [ - "authentication", - "security", - "api", - "sessions" - ] - } - }, - "Database Context": { - "value": { - "question": "What database do we use and why?", - "answer": "We use PostgreSQL as our primary database with Prisma as the ORM. PostgreSQL provides excellent performance, ACID compliance, and supports advanced features like JSON columns and full-text search.", - "tags": [ - "database", - "postgresql", - "prisma", - "architecture" - ] - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Context entry created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "organizationId": { - "type": "string" - }, - "question": { - "type": "string" - }, - "answer": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ] - } - } - }, - "example": { - "id": "ctx_abc123def456", - "organizationId": "org_xyz789uvw012", - "question": "How do we handle user authentication in our application?", - "answer": "We use a hybrid authentication system supporting both API keys and session-based authentication.", - "tags": [ - "authentication", - "security", - "api", - "sessions" - ], - "createdAt": "2024-01-15T10:30:00.000Z", - "updatedAt": "2024-01-15T10:30:00.000Z", - "authType": "apikey" - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "array", - "items": { - "type": "string" - } - }, - "error": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - } - }, - "example": { - "message": [ - "question should not be empty", - "answer should not be empty" - ], - "error": "Bad Request", - "statusCode": 400 - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 401 - } - } - }, - "example": { - "message": "Unauthorized", - "statusCode": 401 - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 404 - } - } - }, - "example": { - "message": "Organization not found", - "statusCode": 404 - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 500 - } - } - }, - "example": { - "message": "Internal server error", - "statusCode": 500 - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new context entry", - "tags": [ - "Context" - ] - } - }, - "/v1/context/{id}": { - "get": { - "description": "Returns a specific context entry by ID for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "ContextController_getContextById_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Context entry ID", - "schema": { - "example": "ctx_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Context entry retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "ctx_abc123def456" - }, - "organizationId": { - "type": "string", - "example": "org_xyz789uvw012" - }, - "question": { - "type": "string" - }, - "answer": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "authentication", - "security" - ] - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ] - } - } - }, - "example": { - "id": "ctx_abc123def456", - "organizationId": "org_xyz789uvw012", - "question": "How do we handle user authentication in our application?", - "answer": "We use a hybrid authentication system supporting both API keys and session-based authentication.", - "tags": [ - "authentication", - "security", - "api", - "sessions" - ], - "createdAt": "2024-01-15T10:30:00.000Z", - "updatedAt": "2024-01-15T14:20:00.000Z", - "authType": "apikey" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - }, - "statusCode": { - "type": "number", - "example": 401 - } - } - }, - "example": { - "message": "Unauthorized", - "statusCode": 401 - } - } - } - }, - "404": { - "description": "Context entry not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 404 - } - } - }, - "example": { - "message": "Context entry with ID ctx_abc123def456 not found in organization org_xyz789uvw012", - "statusCode": 404 - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 500 - } - } - }, - "example": { - "message": "Internal server error", - "statusCode": 500 - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get context entry by ID", - "tags": [ - "Context" - ] - }, - "patch": { - "description": "Partially updates a context entry. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "ContextController_updateContext_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Context entry ID", - "schema": { - "example": "ctx_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Partial context entry data to update", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateContextDto" - }, - "examples": { - "Update Tags": { - "value": { - "tags": [ - "authentication", - "security", - "api", - "sessions", - "updated" - ] - } - }, - "Update Answer": { - "value": { - "answer": "Updated: We use a hybrid authentication system supporting both API keys and session-based authentication. Recent updates include support for OAuth2 providers." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Context entry updated successfully", - "content": { - "application/json": { - "example": { - "id": "ctx_abc123def456", - "organizationId": "org_xyz789uvw012", - "question": "How do we handle user authentication in our application?", - "answer": "Updated: We use a hybrid authentication system supporting both API keys and session-based authentication with OAuth2 support.", - "tags": [ - "authentication", - "security", - "api", - "sessions", - "oauth2" - ], - "createdAt": "2024-01-15T10:30:00.000Z", - "updatedAt": "2024-01-15T15:45:00.000Z", - "authType": "apikey" - } - } - } - }, - "400": { - "description": "Bad request - Invalid input data", - "content": { - "application/json": { - "example": { - "message": [ - "tags must be an array of strings" - ], - "error": "Bad Request", - "statusCode": 400 - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "example": { - "message": "Unauthorized", - "statusCode": 401 - } - } - } - }, - "404": { - "description": "Context entry not found", - "content": { - "application/json": { - "example": { - "message": "Context entry with ID ctx_abc123def456 not found in organization org_xyz789uvw012", - "statusCode": 404 - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "example": { - "message": "Internal server error", - "statusCode": 500 - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update context entry", - "tags": [ - "Context" - ] - }, - "delete": { - "description": "Permanently removes a context entry from the organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "ContextController_deleteContext_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Context entry ID", - "schema": { - "example": "ctx_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Context entry deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "deletedContext": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "question": { - "type": "string" - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ] - } - } - }, - "example": { - "message": "Context entry deleted successfully", - "deletedContext": { - "id": "ctx_abc123def456", - "question": "How do we handle user authentication in our application?" - }, - "authType": "apikey" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 401 - } - } - }, - "example": { - "message": "Unauthorized", - "statusCode": 401 - } - } - } - }, - "404": { - "description": "Context entry not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 404 - } - } - }, - "example": { - "message": "Context entry with ID ctx_abc123def456 not found in organization org_xyz789uvw012", - "statusCode": 404 - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number", - "example": 500 - } - } - }, - "example": { - "message": "Internal server error", - "statusCode": 500 - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete context entry", - "tags": [ - "Context" - ] - } - }, - "/v1/devices": { - "get": { - "description": "Returns all devices for the authenticated organization from FleetDM. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "DevicesController_getAllDevices_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Devices retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DeviceResponseDto" - } - }, - "count": { - "type": "number", - "description": "Total number of devices", - "example": 25 - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - }, - "authenticatedUser": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "User ID", - "example": "usr_abc123def456" - }, - "email": { - "type": "string", - "description": "User email", - "example": "user@company.com" - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid or expired API key" - } - } - } - } - } - }, - "404": { - "description": "Organization not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization with ID org_abc123def456 not found" - } - } - } - } - } - }, - "500": { - "description": "Internal server error - FleetDM integration issue", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Organization does not have FleetDM configured" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all devices", - "tags": [ - "Devices" - ] - } - }, - "/v1/devices/member/{memberId}": { - "get": { - "description": "Returns all devices assigned to a specific member within the authenticated organization. Devices are fetched from FleetDM using the member's dedicated fleetDmLabelId. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "DevicesController_getDevicesByMember_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "memberId", - "required": true, - "in": "path", - "description": "Member ID to get devices for", - "schema": { - "example": "mem_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Member devices retrieved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DevicesByMemberResponseDto" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Organization or member not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Member with ID mem_abc123def456 not found in organization org_abc123def456" - } - } - } - } - } - }, - "500": { - "description": "Internal server error - FleetDM integration issue" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get devices by member ID", - "tags": [ - "Devices" - ] - } - }, - "/v1/policies": { - "get": { - "description": "Returns all policies for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PoliciesController_getAllPolicies_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Policies retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PolicyResponseDto" - } - }, - "example": [ - { - "id": "pol_abc123def456", - "name": "Data Privacy Policy", - "status": "draft", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "..." - } - ] - } - ], - "isRequiredToSign": true, - "signedBy": [], - "createdAt": "2024-01-01T00:00:00.000Z", - "updatedAt": "2024-01-15T00:00:00.000Z", - "organizationId": "org_abc123def456" - } - ] - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all policies", - "tags": [ - "Policies" - ] - }, - "post": { - "description": "Creates a new policy for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PoliciesController_createPolicy_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Policy creation data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreatePolicyDto" - } - } - } - }, - "responses": { - "201": { - "description": "Policy created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PolicyResponseDto" - }, - "example": { - "id": "pol_abc123def456", - "name": "Data Privacy Policy", - "description": "This policy outlines how we handle and protect personal data", - "status": "draft", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Policy content here" - } - ] - } - ], - "frequency": "yearly", - "department": "it", - "isRequiredToSign": true, - "signedBy": [], - "reviewDate": "2024-12-31T00:00:00.000Z", - "isArchived": false, - "createdAt": "2024-01-01T00:00:00.000Z", - "updatedAt": "2024-01-15T00:00:00.000Z", - "organizationId": "org_abc123def456", - "assigneeId": "usr_abc123def456", - "approverId": "usr_xyz789abc123" - } - } - } - }, - "400": { - "description": "Bad Request - Invalid policy data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid policy content format" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new policy", - "tags": [ - "Policies" - ] - } - }, - "/v1/policies/{id}": { - "get": { - "description": "Returns a specific policy by ID for the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PoliciesController_getPolicy_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Policy ID", - "schema": { - "example": "pol_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Policy retrieved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PolicyResponseDto" - }, - "example": { - "id": "pol_abc123def456", - "name": "Data Privacy Policy", - "status": "draft", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "..." - } - ] - } - ], - "isRequiredToSign": true, - "signedBy": [], - "createdAt": "2024-01-01T00:00:00.000Z", - "updatedAt": "2024-01-15T00:00:00.000Z", - "organizationId": "org_abc123def456" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Policy not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Policy with ID pol_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get policy by ID", - "tags": [ - "Policies" - ] - }, - "patch": { - "description": "Partially updates a policy. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PoliciesController_updatePolicy_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Policy ID", - "schema": { - "example": "pol_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Policy update data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdatePolicyDto" - } - } - } - }, - "responses": { - "200": { - "description": "Policy updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PolicyResponseDto" - }, - "example": { - "id": "pol_abc123def456", - "name": "Data Privacy Policy", - "description": "This policy outlines how we handle and protect personal data", - "status": "published", - "content": [ - { - "type": "heading", - "attrs": { - "level": 2 - }, - "content": [ - { - "type": "text", - "text": "Purpose" - } - ] - } - ], - "frequency": "yearly", - "department": "it", - "isRequiredToSign": true, - "signedBy": [ - "usr_123" - ], - "reviewDate": "2024-12-31T00:00:00.000Z", - "isArchived": false, - "createdAt": "2024-01-01T00:00:00.000Z", - "updatedAt": "2024-01-15T00:00:00.000Z", - "organizationId": "org_abc123def456", - "assigneeId": "usr_abc123def456", - "approverId": "usr_xyz789abc123" - } - } - } - }, - "400": { - "description": "Bad Request - Invalid update data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Validation failed" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Policy not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Policy with ID pol_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update policy", - "tags": [ - "Policies" - ] - }, - "delete": { - "description": "Permanently deletes a policy. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "PoliciesController_deletePolicy_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Policy ID", - "schema": { - "example": "pol_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Policy deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "description": "Indicates successful deletion", - "example": true - }, - "deletedPolicy": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The deleted policy ID", - "example": "pol_abc123def456" - }, - "name": { - "type": "string", - "description": "The deleted policy name", - "example": "Data Privacy Policy" - } - } - }, - "authType": { - "type": "string", - "enum": [ - "api-key", - "session" - ], - "description": "How the request was authenticated" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication or insufficient permissions", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Policy not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Policy with ID pol_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete policy", - "tags": [ - "Policies" - ] - } - }, - "/v1/policies/{id}/ai-chat": { - "post": { - "description": "Stream AI responses for policy editing assistance. Returns a text/event-stream with AI-generated suggestions.", - "operationId": "PoliciesController_aiChatPolicy_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Policy ID", - "schema": { - "example": "pol_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AISuggestPolicyRequestDto" - } - } - } - }, - "responses": { - "200": { - "description": "Streaming AI response", - "content": { - "text/event-stream": { - "schema": { - "type": "string" - } - } - } - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Policy not found" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Chat with AI about a policy", - "tags": [ - "Policies" - ] - } - }, - "/v1/device-agent/mac": { - "get": { - "description": "Downloads the Comp AI Device Agent installer for macOS as a DMG file. The agent helps monitor device compliance and security policies. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "DeviceAgentController_downloadMacAgent_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "macOS agent DMG file download", - "content": { - "application/x-apple-diskimage": { - "schema": { - "type": "string", - "format": "binary" - }, - "example": "Binary DMG file content" - } - }, - "headers": { - "Content-Disposition": { - "description": "Indicates file should be downloaded with specific filename", - "schema": { - "type": "string", - "example": "attachment; filename=\"Comp AI Agent-1.0.0-arm64.dmg\"" - } - }, - "Content-Type": { - "description": "MIME type for macOS disk image", - "schema": { - "type": "string", - "example": "application/x-apple-diskimage" - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Download macOS Device Agent", - "tags": [ - "Device Agent" - ] - } - }, - "/v1/device-agent/windows": { - "get": { - "description": "Downloads a ZIP package containing the Comp AI Device Agent installer for Windows, along with setup scripts and instructions. The package includes an MSI installer, setup batch script customized for the organization and user, and a README with installation instructions. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).", - "operationId": "DeviceAgentController_downloadWindowsAgent_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "Windows agent ZIP file download containing MSI installer and setup scripts", - "content": { - "application/zip": { - "schema": { - "type": "string", - "format": "binary" - }, - "example": "Binary ZIP file content" - } - }, - "headers": { - "Content-Disposition": { - "description": "Indicates file should be downloaded with specific filename", - "schema": { - "type": "string", - "example": "attachment; filename=\"compai-device-agent-windows.zip\"" - } - }, - "Content-Type": { - "description": "MIME type for ZIP archive", - "schema": { - "type": "string", - "example": "application/zip" - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Download Windows Device Agent ZIP", - "tags": [ - "Device Agent" - ] - } - }, - "/v1/attachments/{attachmentId}/download": { - "get": { - "description": "Generate a fresh signed URL for downloading any attachment", - "operationId": "AttachmentsController_getAttachmentDownloadUrl_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "attachmentId", - "required": true, - "in": "path", - "description": "Unique attachment identifier", - "schema": { - "example": "att_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Download URL generated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "downloadUrl": { - "type": "string", - "description": "Signed URL for downloading the file", - "example": "https://bucket.s3.amazonaws.com/path/to/file.pdf?signature=..." - }, - "expiresIn": { - "type": "number", - "description": "URL expiration time in seconds", - "example": 900 - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get attachment download URL", - "tags": [ - "Attachments" - ] - } - }, - "/v1/tasks": { - "get": { - "description": "Retrieve all tasks for the authenticated organization", - "operationId": "TasksController_getTasks_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Tasks retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TaskResponseDto" - } - }, - "example": [ - { - "id": "tsk_abc123def456", - "title": "Implement user authentication", - "description": "Add OAuth 2.0 authentication to the platform", - "status": "in_progress", - "createdAt": "2024-01-15T10:30:00Z", - "updatedAt": "2024-01-15T10:30:00Z" - } - ] - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all tasks", - "tags": [ - "Tasks" - ] - } - }, - "/v1/tasks/{taskId}": { - "get": { - "description": "Retrieve a specific task by its ID", - "operationId": "TasksController_getTask_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Task retrieved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TaskResponseDto" - }, - "example": { - "id": "tsk_abc123def456", - "title": "Implement user authentication", - "description": "Add OAuth 2.0 authentication to the platform", - "status": "in_progress", - "createdAt": "2024-01-15T10:30:00Z", - "updatedAt": "2024-01-15T10:30:00Z" - } - } - } - }, - "404": { - "description": "Task not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task with ID tsk_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get task by ID", - "tags": [ - "Tasks" - ] - } - }, - "/v1/tasks/{taskId}/attachments": { - "get": { - "description": "Retrieve all attachments for a specific task", - "operationId": "TasksController_getTaskAttachments_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Attachments retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AttachmentResponseDto" - } - }, - "example": [ - { - "id": "att_abc123def456", - "name": "evidence.pdf", - "type": "application/pdf", - "size": 123456, - "downloadUrl": "https://bucket.s3.amazonaws.com/path/to/file.pdf?signature=...", - "createdAt": "2024-01-15T10:30:00Z" - } - ] - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Task not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task with ID tsk_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get task attachments", - "tags": [ - "Tasks" - ] - }, - "post": { - "description": "Upload a file attachment to a specific task", - "operationId": "TasksController_uploadTaskAttachment_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UploadAttachmentDto" - } - } - } - }, - "responses": { - "201": { - "description": "Attachment uploaded successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AttachmentResponseDto" - }, - "example": { - "id": "att_abc123def456", - "entityId": "tsk_abc123def456", - "entityType": "task", - "fileName": "evidence.pdf", - "fileType": "application/pdf", - "fileSize": 123456, - "createdAt": "2024-01-01T00:00:00Z", - "createdBy": "usr_abc123def456" - } - } - } - }, - "400": { - "description": "Invalid file data or file too large", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "File exceeds maximum allowed size" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Task not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task with ID tsk_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Upload attachment to task", - "tags": [ - "Tasks" - ] - } - }, - "/v1/tasks/{taskId}/attachments/{attachmentId}/download": { - "get": { - "description": "Generate a signed URL for downloading a task attachment", - "operationId": "TasksController_getTaskAttachmentDownloadUrl_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "attachmentId", - "required": true, - "in": "path", - "description": "Unique attachment identifier", - "schema": { - "example": "att_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Download URL generated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "downloadUrl": { - "type": "string", - "description": "Signed URL for downloading the file", - "example": "https://bucket.s3.amazonaws.com/path/to/file.pdf?signature=..." - }, - "expiresIn": { - "type": "number", - "description": "URL expiration time in seconds", - "example": 900 - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Task or attachment not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task or attachment not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get attachment download URL", - "tags": [ - "Tasks" - ] - } - }, - "/v1/tasks/{taskId}/attachments/{attachmentId}": { - "delete": { - "description": "Delete a specific attachment from a task", - "operationId": "TasksController_deleteTaskAttachment_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "attachmentId", - "required": true, - "in": "path", - "description": "Unique attachment identifier", - "schema": { - "example": "att_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Attachment deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "deletedAttachmentId": { - "type": "string", - "example": "att_abc123def456" - }, - "message": { - "type": "string", - "example": "Attachment deleted successfully" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Task or attachment not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task or attachment not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete task attachment", - "tags": [ - "Tasks" - ] - } - }, - "/v1/tasks/{taskId}/automations": { - "get": { - "description": "Retrieve all automations for a specific task", - "operationId": "AutomationsController_getTaskAutomations_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Automations retrieved successfully" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all automations for a task", - "tags": [ - "Task Automations" - ] - }, - "post": { - "description": "Create an automation for collecting evidence for a specific task", - "operationId": "AutomationsController_createAutomation_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Automation created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "automation": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "auto_abc123def456" - }, - "name": { - "type": "string", - "example": "Task Name - Evidence Collection" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid task ID or organization ID", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid task ID or organization ID" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Task not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Task not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new evidence automation", - "tags": [ - "Task Automations" - ] - } - }, - "/v1/tasks/{taskId}/automations/{automationId}": { - "get": { - "description": "Retrieve details for a specific automation", - "operationId": "AutomationsController_getAutomation_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "automationId", - "required": true, - "in": "path", - "description": "Unique automation identifier", - "schema": { - "example": "auto_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Automation details retrieved successfully" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get automation details", - "tags": [ - "Task Automations" - ] - }, - "patch": { - "description": "Update the name or description of an existing automation", - "operationId": "AutomationsController_updateAutomation_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "automationId", - "required": true, - "in": "path", - "description": "Unique automation identifier", - "schema": { - "example": "auto_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateAutomationDto" - } - } - } - }, - "responses": { - "200": { - "description": "Automation updated successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "automation": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "auto_abc123def456" - }, - "name": { - "type": "string", - "example": "Updated Automation Name" - }, - "description": { - "type": "string", - "example": "Updated description" - } - } - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid automation ID or data", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Invalid automation data" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Automation not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Automation not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update an existing automation", - "tags": [ - "Task Automations" - ] - }, - "delete": { - "description": "Delete a specific automation and all its associated data", - "operationId": "AutomationsController_deleteAutomation_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Unique task identifier", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "automationId", - "required": true, - "in": "path", - "description": "Unique automation identifier", - "schema": { - "example": "auto_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Automation deleted successfully" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete an automation", - "tags": [ - "Task Automations" - ] - } - }, - "/v1/tasks/{taskId}/automations/{automationId}/versions": { - "get": { - "description": "Retrieve all published versions of an automation script", - "operationId": "AutomationsController_getAutomationVersions_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Task ID", - "schema": { - "type": "string" - } - }, - { - "name": "automationId", - "required": true, - "in": "path", - "description": "Automation ID", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "offset", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Versions retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "versions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "version": { - "type": "number" - }, - "scriptKey": { - "type": "string" - }, - "changelog": { - "type": "string", - "nullable": true - }, - "publishedBy": { - "type": "string", - "nullable": true - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all versions for an automation", - "tags": [ - "Task Automations" - ] - } - }, - "/v1/tasks/{taskId}/automations/runs": { - "get": { - "description": "Retrieve all evidence automation runs across automations for a specific task", - "operationId": "AutomationsController_getTaskAutomationRuns_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "taskId", - "required": true, - "in": "path", - "description": "Task ID", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Automation runs retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "ear_abc123def456" - }, - "status": { - "type": "string", - "enum": [ - "PENDING", - "RUNNING", - "COMPLETED", - "FAILED" - ] - }, - "trigger": { - "type": "string", - "enum": [ - "MANUAL", - "SCHEDULED", - "EVENT" - ] - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "completedAt": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "error": { - "type": "object", - "nullable": true - } - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all automation runs for a task", - "tags": [ - "Task Automations" - ] - } - }, - "/v1/comments": { - "get": { - "description": "Retrieve all comments for a specific entity (task, policy, vendor, etc.)", - "operationId": "CommentsController_getComments_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "entityId", - "required": true, - "in": "query", - "description": "ID of the entity to get comments for", - "schema": { - "example": "tsk_abc123def456", - "type": "string" - } - }, - { - "name": "entityType", - "required": true, - "in": "query", - "description": "Type of entity", - "schema": { - "enum": [ - "task", - "vendor", - "risk", - "policy" - ], - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Comments retrieved successfully", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CommentResponseDto" - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get comments for an entity", - "tags": [ - "Comments" - ] - }, - "post": { - "description": "Create a comment on an entity with optional file attachments", - "operationId": "CommentsController_createComment_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCommentDto" - } - } - } - }, - "responses": { - "201": { - "description": "Comment created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CommentResponseDto" - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Create a new comment", - "tags": [ - "Comments" - ] - } - }, - "/v1/comments/{commentId}": { - "put": { - "description": "Update the content of an existing comment (author only)", - "operationId": "CommentsController_updateComment_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "commentId", - "required": true, - "in": "path", - "description": "Unique comment identifier", - "schema": { - "example": "cmt_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCommentDto" - } - } - } - }, - "responses": { - "200": { - "description": "Comment updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CommentResponseDto" - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update a comment", - "tags": [ - "Comments" - ] - }, - "delete": { - "description": "Delete a comment and all its attachments (author only)", - "operationId": "CommentsController_deleteComment_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "commentId", - "required": true, - "in": "path", - "description": "Unique comment identifier", - "schema": { - "example": "cmt_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Comment deleted successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "deletedCommentId": { - "type": "string", - "example": "cmt_abc123def456" - }, - "message": { - "type": "string", - "example": "Comment deleted successfully" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid authentication", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Unauthorized" - } - } - } - } - } - }, - "404": { - "description": "Comment not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Comment with ID cmt_abc123def456 not found" - } - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete a comment", - "tags": [ - "Comments" - ] - } - }, - "/v1/health": { - "get": { - "description": "Returns the health status of the API", - "operationId": "HealthController_getHealth_v1", - "parameters": [], - "responses": { - "200": { - "description": "API is healthy", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "example": "ok" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "uptime": { - "type": "number", - "description": "Process uptime in seconds" - }, - "version": { - "type": "string", - "example": "1.0.0" - } - } - } - } - } - } - }, - "summary": "Health check", - "tags": [ - "Health" - ] - } - }, - "/v1/trust-portal/domain/status": { - "get": { - "description": "Retrieve the verification status and DNS records for a custom domain configured in the Vercel trust portal project", - "operationId": "TrustPortalController_getDomainStatus_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "domain", - "required": true, - "in": "query", - "description": "The domain name to check status for", - "schema": { - "example": "portal.example.com", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Domain status retrieved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DomainStatusResponseDto" - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication" - }, - "500": { - "description": "Failed to retrieve domain status from Vercel" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get domain verification status", - "tags": [ - "Trust Portal" - ] - } - }, - "/v1/trust-access/{friendlyUrl}/requests": { - "post": { - "description": "External users submit request for data access from trust site", - "operationId": "TrustAccessController_createAccessRequest_v1", - "parameters": [ - { - "name": "friendlyUrl", - "required": true, - "in": "path", - "description": "Trust Portal friendly URL or Organization ID", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateAccessRequestDto" - } - } - } - }, - "responses": { - "201": { - "description": "Access request created and sent for review" - } - }, - "summary": "Submit data access request", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests": { - "get": { - "description": "Get all access requests for organization", - "operationId": "TrustAccessController_listAccessRequests_v1", - "parameters": [ - { - "name": "status", - "required": false, - "in": "query", - "schema": { - "type": "string", - "enum": [ - "under_review", - "approved", - "denied", - "canceled" - ] - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Access requests retrieved" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "List access requests", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests/{id}": { - "get": { - "description": "Get detailed information about a specific access request", - "operationId": "TrustAccessController_getAccessRequest_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Request details returned" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get access request details", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests/{id}/approve": { - "post": { - "description": "Approve request and create time-limited grant", - "operationId": "TrustAccessController_approveRequest_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ApproveAccessRequestDto" - } - } - } - }, - "responses": { - "200": { - "description": "Request approved successfully" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Approve access request", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests/{id}/deny": { - "post": { - "description": "Reject access request with reason", - "operationId": "TrustAccessController_denyRequest_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DenyAccessRequestDto" - } - } - } - }, - "responses": { - "200": { - "description": "Request denied" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Deny access request", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/grants": { - "get": { - "description": "Get all active and expired grants", - "operationId": "TrustAccessController_listGrants_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Grants retrieved" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "List access grants", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/grants/{id}/revoke": { - "post": { - "description": "Immediately revoke active grant", - "operationId": "TrustAccessController_revokeGrant_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RevokeGrantDto" - } - } - } - }, - "responses": { - "200": { - "description": "Grant revoked" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Revoke access grant", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/nda/{token}": { - "get": { - "description": "Fetch NDA agreement details for signing", - "operationId": "TrustAccessController_getNda_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "NDA details returned" - } - }, - "summary": "Get NDA details by token", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/nda/{token}/preview-nda": { - "post": { - "description": "Generate preview NDA PDF for external user before signing", - "operationId": "TrustAccessController_previewNdaByToken_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Preview NDA generated" - } - }, - "summary": "Preview NDA by token", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/nda/{token}/sign": { - "post": { - "description": "Sign NDA agreement, generate watermarked PDF, and create access grant", - "operationId": "TrustAccessController_signNda_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SignNdaDto" - } - } - } - }, - "responses": { - "200": { - "description": "NDA signed successfully" - } - }, - "summary": "Sign NDA", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests/{id}/resend-nda": { - "post": { - "description": "Resend NDA signing email to requester", - "operationId": "TrustAccessController_resendNda_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "NDA email resent" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Resend NDA email", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/admin/requests/{id}/preview-nda": { - "post": { - "description": "Generate preview NDA with watermark and save to S3 with preview-* prefix", - "operationId": "TrustAccessController_previewNda_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Preview NDA generated" - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Preview NDA PDF", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/{friendlyUrl}/reclaim": { - "post": { - "description": "Generate access link for users with existing grants to redownload data", - "operationId": "TrustAccessController_reclaimAccess_v1", - "parameters": [ - { - "name": "friendlyUrl", - "required": true, - "in": "path", - "description": "Trust Portal friendly URL or Organization ID", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ReclaimAccessDto" - } - } - } - }, - "responses": { - "200": { - "description": "Access link sent to email" - } - }, - "summary": "Reclaim access", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/access/{token}": { - "get": { - "description": "Retrieve compliance data using access token", - "operationId": "TrustAccessController_getGrantByAccessToken_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Grant data returned" - } - }, - "summary": "Get grant data by access token", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/access/{token}/policies": { - "get": { - "description": "Get list of published policies available for download", - "operationId": "TrustAccessController_getPoliciesByAccessToken_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Policies list returned" - } - }, - "summary": "List policies by access token", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/trust-access/access/{token}/policies/download-all": { - "get": { - "description": "Generate combined PDF from all published policy content with watermark", - "operationId": "TrustAccessController_downloadAllPolicies_v1", - "parameters": [ - { - "name": "token", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Download URL for watermarked PDF returned" - } - }, - "summary": "Download all policies as watermarked PDF", - "tags": [ - "Trust Access" - ] - } - }, - "/v1/framework-editor/task-template": { - "get": { - "description": "Retrieve all framework editor task templates", - "operationId": "TaskTemplateController_getAllTaskTemplates_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successfully retrieved all framework editor task templates", - "content": { - "application/json": { - "schema": { - "example": { - "data": [ - { - "id": "frk_tt_abc123def456", - "name": "Monthly Security Review", - "description": "Review and update security policies on a monthly basis", - "frequency": "monthly", - "department": "it", - "createdAt": "2025-01-01T00:00:00.000Z", - "updatedAt": "2025-01-01T00:00:00.000Z" - } - ], - "count": 1, - "authType": "session", - "authenticatedUser": { - "id": "user_123", - "email": "user@example.com" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 401, - "message": "Unauthorized" - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 500, - "message": "Internal server error" - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get all framework editor task templates", - "tags": [ - "Framework Editor Task Templates" - ] - } - }, - "/v1/framework-editor/task-template/{id}": { - "get": { - "description": "Retrieve a specific framework editor task template by its ID", - "operationId": "TaskTemplateController_getTaskTemplateById_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Framework editor task template ID", - "schema": { - "example": "frk_tt_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successfully retrieved framework editor task template", - "content": { - "application/json": { - "schema": { - "example": { - "id": "frk_tt_abc123def456", - "name": "Monthly Security Review", - "description": "Review and update security policies on a monthly basis", - "frequency": "monthly", - "department": "it", - "createdAt": "2025-01-01T00:00:00.000Z", - "updatedAt": "2025-01-01T00:00:00.000Z", - "authType": "session", - "authenticatedUser": { - "id": "user_123", - "email": "user@example.com" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 401, - "message": "Unauthorized" - } - } - } - } - }, - "404": { - "description": "Framework editor task template not found", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 404, - "message": "Framework editor task template with ID frk_tt_abc123def456 not found" - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 500, - "message": "Internal server error" - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Get framework editor task template by ID", - "tags": [ - "Framework Editor Task Templates" - ] - }, - "patch": { - "description": "Update a framework editor task template by ID", - "operationId": "TaskTemplateController_updateTaskTemplate_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Framework editor task template ID", - "schema": { - "example": "frk_tt_abc123def456", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "description": "Update framework editor task template data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateTaskTemplateDto" - } - } - } - }, - "responses": { - "200": { - "description": "Successfully updated framework editor task template", - "content": { - "application/json": { - "schema": { - "example": { - "id": "frk_tt_abc123def456", - "name": "Monthly Security Review (Updated)", - "description": "Review and update security policies on a monthly basis", - "frequency": "monthly", - "department": "it", - "createdAt": "2025-01-01T00:00:00.000Z", - "updatedAt": "2025-01-02T00:00:00.000Z", - "authType": "session", - "authenticatedUser": { - "id": "user_123", - "email": "user@example.com" - } - } - } - } - } - }, - "400": { - "description": "Bad request - Invalid data provided", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 400, - "message": "Validation failed" - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 401, - "message": "Unauthorized" - } - } - } - } - }, - "404": { - "description": "Framework editor task template not found", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 404, - "message": "Framework editor task template with ID frk_tt_abc123def456 not found" - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 500, - "message": "Internal server error" - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Update framework editor task template", - "tags": [ - "Framework Editor Task Templates" - ] - }, - "delete": { - "description": "Delete a framework editor task template by ID", - "operationId": "TaskTemplateController_deleteTaskTemplate_v1", - "parameters": [ - { - "name": "X-Organization-Id", - "in": "header", - "description": "Organization ID (required for session auth, optional for API key auth)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "required": true, - "in": "path", - "description": "Framework editor task template ID", - "schema": { - "example": "frk_tt_abc123def456", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successfully deleted framework editor task template", - "content": { - "application/json": { - "schema": { - "example": { - "message": "Framework editor task template deleted successfully", - "deletedTaskTemplate": { - "id": "frk_tt_abc123def456", - "name": "Monthly Security Review" - }, - "authType": "session", - "authenticatedUser": { - "id": "user_123", - "email": "user@example.com" - } - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid or missing authentication", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 401, - "message": "Unauthorized" - } - } - } - } - }, - "404": { - "description": "Framework editor task template not found", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 404, - "message": "Framework editor task template with ID frk_tt_abc123def456 not found" - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "example": { - "statusCode": 500, - "message": "Internal server error" - } - } - } - } - } - }, - "security": [ - { - "apikey": [] - } - ], - "summary": "Delete framework editor task template", - "tags": [ - "Framework Editor Task Templates" - ] - } - }, - "/v1/integrations/oauth/availability": { - "get": { - "operationId": "OAuthController_checkAvailability_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "OAuth" - ] - } - }, - "/v1/integrations/oauth/start": { - "post": { - "operationId": "OAuthController_startOAuth_v1", - "parameters": [], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "OAuth" - ] - } - }, - "/v1/integrations/oauth/callback": { - "get": { - "operationId": "OAuthController_oauthCallback_v1", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "OAuth" - ] - } - }, - "/v1/integrations/oauth-apps": { - "get": { - "operationId": "OAuthAppsController_listOAuthApps_v1", - "parameters": [ - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "OAuthApps" - ] - }, - "post": { - "operationId": "OAuthAppsController_saveOAuthApp_v1", - "parameters": [], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "OAuthApps" - ] - } - }, - "/v1/integrations/oauth-apps/setup/{providerSlug}": { - "get": { - "operationId": "OAuthAppsController_getSetupInfo_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "OAuthApps" - ] - } - }, - "/v1/integrations/oauth-apps/{providerSlug}": { - "delete": { - "operationId": "OAuthAppsController_deleteOAuthApp_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "OAuthApps" - ] - } - }, - "/v1/integrations/connections/providers": { - "get": { - "operationId": "ConnectionsController_listProviders_v1", - "parameters": [ - { - "name": "activeOnly", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/providers/{slug}": { - "get": { - "operationId": "ConnectionsController_getProvider_v1", - "parameters": [ - { - "name": "slug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections": { - "get": { - "operationId": "ConnectionsController_listConnections_v1", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - }, - "post": { - "operationId": "ConnectionsController_createConnection_v1", - "parameters": [], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/{id}": { - "get": { - "operationId": "ConnectionsController_getConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - }, - "delete": { - "operationId": "ConnectionsController_deleteConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/{id}/test": { - "post": { - "operationId": "ConnectionsController_testConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/{id}/pause": { - "post": { - "operationId": "ConnectionsController_pauseConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/{id}/resume": { - "post": { - "operationId": "ConnectionsController_resumeConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/integrations/connections/{id}/disconnect": { - "post": { - "operationId": "ConnectionsController_disconnectConnection_v1", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Connections" - ] - } - }, - "/v1/admin/integrations": { - "get": { - "operationId": "AdminIntegrationsController_listIntegrations_v1", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "AdminIntegrations" - ] - } - }, - "/v1/admin/integrations/{providerSlug}": { - "get": { - "operationId": "AdminIntegrationsController_getIntegration_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "AdminIntegrations" - ] - } - }, - "/v1/admin/integrations/credentials": { - "post": { - "operationId": "AdminIntegrationsController_savePlatformCredentials_v1", - "parameters": [], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "AdminIntegrations" - ] - } - }, - "/v1/admin/integrations/credentials/{providerSlug}": { - "delete": { - "operationId": "AdminIntegrationsController_deletePlatformCredentials_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "AdminIntegrations" - ] - } - }, - "/v1/integrations/checks/providers/{providerSlug}": { - "get": { - "operationId": "ChecksController_listProviderChecks_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Checks" - ] - } - }, - "/v1/integrations/checks/connections/{connectionId}": { - "get": { - "operationId": "ChecksController_listConnectionChecks_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Checks" - ] - } - }, - "/v1/integrations/checks/connections/{connectionId}/run": { - "post": { - "operationId": "ChecksController_runConnectionChecks_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Checks" - ] - } - }, - "/v1/integrations/checks/connections/{connectionId}/run/{checkId}": { - "post": { - "operationId": "ChecksController_runSingleCheck_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "checkId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Checks" - ] - } - }, - "/v1/integrations/variables/providers/{providerSlug}": { - "get": { - "operationId": "VariablesController_getProviderVariables_v1", - "parameters": [ - { - "name": "providerSlug", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Variables" - ] - } - }, - "/v1/integrations/variables/connections/{connectionId}": { - "get": { - "operationId": "VariablesController_getConnectionVariables_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Variables" - ] - }, - "post": { - "operationId": "VariablesController_saveConnectionVariables_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "Variables" - ] - } - }, - "/v1/integrations/variables/connections/{connectionId}/options/{variableId}": { - "get": { - "operationId": "VariablesController_fetchVariableOptions_v1", - "parameters": [ - { - "name": "connectionId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "variableId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Variables" - ] - } - }, - "/v1/integrations/tasks/template/{templateId}/checks": { - "get": { - "operationId": "TaskIntegrationsController_getChecksForTaskTemplate_v1", - "parameters": [ - { - "name": "templateId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "TaskIntegrations" - ] - } - }, - "/v1/integrations/tasks/{taskId}/checks": { - "get": { - "operationId": "TaskIntegrationsController_getChecksForTask_v1", - "parameters": [ - { - "name": "taskId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "TaskIntegrations" - ] - } - }, - "/v1/integrations/tasks/{taskId}/run-check": { - "post": { - "operationId": "TaskIntegrationsController_runCheckForTask_v1", - "parameters": [ - { - "name": "taskId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "TaskIntegrations" - ] - } - }, - "/v1/integrations/tasks/{taskId}/runs": { - "get": { - "operationId": "TaskIntegrationsController_getTaskCheckRuns_v1", - "parameters": [ - { - "name": "taskId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "organizationId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "TaskIntegrations" - ] - } - } - }, - "info": { - "title": "API Documentation", - "description": "The API documentation for this application", - "version": "1.0", - "contact": {} - }, - "tags": [], - "servers": [ - { - "url": "https://api.trycomp.ai", - "description": "API Server" - } - ], - "components": { - "securitySchemes": { - "apikey": { - "type": "apiKey", - "in": "header", - "name": "X-API-Key", - "description": "API key for authentication" - } - }, - "schemas": { - "UserResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "User ID", - "example": "usr_abc123def456" - }, - "name": { - "type": "string", - "description": "User name", - "example": "John Doe" - }, - "email": { - "type": "string", - "description": "User email", - "example": "john.doe@company.com" - }, - "emailVerified": { - "type": "boolean", - "description": "Whether email is verified", - "example": true - }, - "image": { - "type": "object", - "description": "User profile image URL", - "example": "https://example.com/avatar.jpg", - "nullable": true - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "When the user was created", - "example": "2024-01-01T00:00:00Z" - }, - "updatedAt": { - "format": "date-time", - "type": "string", - "description": "When the user was last updated", - "example": "2024-01-15T00:00:00Z" - }, - "lastLogin": { - "type": "object", - "description": "Last login time", - "example": "2024-01-15T12:00:00Z", - "nullable": true - } - }, - "required": [ - "id", - "name", - "email", - "emailVerified", - "image", - "createdAt", - "updatedAt", - "lastLogin" - ] - }, - "PeopleResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Member ID", - "example": "mem_abc123def456" - }, - "organizationId": { - "type": "string", - "description": "Organization ID this member belongs to", - "example": "org_abc123def456" - }, - "userId": { - "type": "string", - "description": "User ID associated with member", - "example": "usr_abc123def456" - }, - "role": { - "type": "string", - "description": "Member role", - "example": "admin" - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "When the member was created", - "example": "2024-01-01T00:00:00Z" - }, - "department": { - "type": "string", - "description": "Member department", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "isActive": { - "type": "boolean", - "description": "Whether member is active", - "example": true - }, - "fleetDmLabelId": { - "type": "object", - "description": "FleetDM label ID for member devices", - "example": 123, - "nullable": true - }, - "user": { - "description": "User information", - "allOf": [ - { - "$ref": "#/components/schemas/UserResponseDto" - } - ] - } - }, - "required": [ - "id", - "organizationId", - "userId", - "role", - "createdAt", - "department", - "isActive", - "fleetDmLabelId", - "user" - ] - }, - "CreatePeopleDto": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "User ID to associate with this member", - "example": "usr_abc123def456" - }, - "role": { - "type": "string", - "description": "Role for the member", - "example": "admin" - }, - "department": { - "type": "string", - "description": "Member department", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "isActive": { - "type": "boolean", - "description": "Whether member is active", - "example": true - }, - "fleetDmLabelId": { - "type": "number", - "description": "FleetDM label ID for member devices", - "example": 123 - } - }, - "required": [ - "userId", - "role" - ] - }, - "BulkCreatePeopleDto": { - "type": "object", - "properties": { - "members": { - "description": "Array of members to create", - "example": [ - { - "userId": "usr_abc123def456", - "role": "admin", - "department": "it", - "isActive": true, - "fleetDmLabelId": 123 - }, - { - "userId": "usr_def456ghi789", - "role": "member", - "department": "hr", - "isActive": true - } - ], - "type": "array", - "items": { - "$ref": "#/components/schemas/CreatePeopleDto" - } - } - }, - "required": [ - "members" - ] - }, - "UpdatePeopleDto": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "User ID to associate with this member", - "example": "usr_abc123def456" - }, - "role": { - "type": "string", - "description": "Role for the member", - "example": "admin" - }, - "department": { - "type": "string", - "description": "Member department", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "isActive": { - "type": "boolean", - "description": "Whether to deactivate this member (soft delete)", - "example": false - }, - "fleetDmLabelId": { - "type": "number", - "description": "FleetDM label ID for member devices", - "example": 123 - } - } - }, - "CreateRiskDto": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Detailed description of the risk", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "description": "Risk category", - "enum": [ - "customer", - "fraud", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "department": { - "type": "string", - "description": "Department responsible for the risk", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "status": { - "type": "string", - "description": "Current status of the risk", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "default": "open", - "example": "open" - }, - "likelihood": { - "type": "string", - "description": "Likelihood of the risk occurring", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "possible" - }, - "impact": { - "type": "string", - "description": "Impact if the risk materializes", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "major" - }, - "residualLikelihood": { - "type": "string", - "description": "Residual likelihood after treatment", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "description": "Residual impact after treatment", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "minor" - }, - "treatmentStrategyDescription": { - "type": "string", - "description": "Description of the treatment strategy", - "example": "Implement multi-factor authentication and strengthen password requirements" - }, - "treatmentStrategy": { - "type": "string", - "description": "Risk treatment strategy", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "default": "accept", - "example": "mitigate" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - } - }, - "required": [ - "title", - "description", - "category", - "status", - "likelihood", - "impact", - "residualLikelihood", - "residualImpact", - "treatmentStrategy" - ] - }, - "UpdateRiskDto": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "Risk title", - "example": "Data breach vulnerability in user authentication system" - }, - "description": { - "type": "string", - "description": "Detailed description of the risk", - "example": "Weak password requirements could lead to unauthorized access to user accounts" - }, - "category": { - "type": "string", - "description": "Risk category", - "enum": [ - "customer", - "fraud", - "governance", - "operations", - "other", - "people", - "regulatory", - "reporting", - "resilience", - "technology", - "vendor_management" - ], - "example": "technology" - }, - "department": { - "type": "string", - "description": "Department responsible for the risk", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "status": { - "type": "string", - "description": "Current status of the risk", - "enum": [ - "open", - "pending", - "closed", - "archived" - ], - "default": "open", - "example": "open" - }, - "likelihood": { - "type": "string", - "description": "Likelihood of the risk occurring", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "possible" - }, - "impact": { - "type": "string", - "description": "Impact if the risk materializes", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "major" - }, - "residualLikelihood": { - "type": "string", - "description": "Residual likelihood after treatment", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "description": "Residual impact after treatment", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "minor" - }, - "treatmentStrategyDescription": { - "type": "string", - "description": "Description of the treatment strategy", - "example": "Implement multi-factor authentication and strengthen password requirements" - }, - "treatmentStrategy": { - "type": "string", - "description": "Risk treatment strategy", - "enum": [ - "accept", - "avoid", - "mitigate", - "transfer" - ], - "default": "accept", - "example": "mitigate" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to this risk", - "example": "mem_abc123def456" - } - } - }, - "CreateVendorDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Detailed description of the vendor and services provided", - "example": "Cloud infrastructure provider offering AWS-like services including compute, storage, and networking solutions for enterprise customers." - }, - "category": { - "type": "string", - "description": "Vendor category", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "default": "other", - "example": "cloud" - }, - "status": { - "type": "string", - "description": "Assessment status of the vendor", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "default": "not_assessed", - "example": "not_assessed" - }, - "inherentProbability": { - "type": "string", - "description": "Inherent probability of risk before controls", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "description": "Inherent impact of risk before controls", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "description": "Residual probability after controls are applied", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "description": "Residual impact after controls are applied", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "minor" - }, - "website": { - "type": "string", - "description": "Vendor website URL", - "example": "https://www.cloudtechsolutions.com" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - } - }, - "required": [ - "name", - "description", - "category", - "status", - "inherentProbability", - "inherentImpact", - "residualProbability", - "residualImpact" - ] - }, - "UpdateVendorDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Vendor name", - "example": "CloudTech Solutions Inc." - }, - "description": { - "type": "string", - "description": "Detailed description of the vendor and services provided", - "example": "Cloud infrastructure provider offering AWS-like services including compute, storage, and networking solutions for enterprise customers." - }, - "category": { - "type": "string", - "description": "Vendor category", - "enum": [ - "cloud", - "infrastructure", - "software_as_a_service", - "finance", - "marketing", - "sales", - "hr", - "other" - ], - "default": "other", - "example": "cloud" - }, - "status": { - "type": "string", - "description": "Assessment status of the vendor", - "enum": [ - "not_assessed", - "in_progress", - "assessed" - ], - "default": "not_assessed", - "example": "not_assessed" - }, - "inherentProbability": { - "type": "string", - "description": "Inherent probability of risk before controls", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "possible" - }, - "inherentImpact": { - "type": "string", - "description": "Inherent impact of risk before controls", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "moderate" - }, - "residualProbability": { - "type": "string", - "description": "Residual probability after controls are applied", - "enum": [ - "very_unlikely", - "unlikely", - "possible", - "likely", - "very_likely" - ], - "default": "very_unlikely", - "example": "unlikely" - }, - "residualImpact": { - "type": "string", - "description": "Residual impact after controls are applied", - "enum": [ - "insignificant", - "minor", - "moderate", - "major", - "severe" - ], - "default": "insignificant", - "example": "minor" - }, - "website": { - "type": "string", - "description": "Vendor website URL", - "example": "https://www.cloudtechsolutions.com" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to manage this vendor", - "example": "mem_abc123def456" - } - } - }, - "CreateContextDto": { - "type": "object", - "properties": { - "question": { - "type": "string", - "description": "The question or topic this context entry addresses", - "example": "How do we handle user authentication in our application?" - }, - "answer": { - "type": "string", - "description": "The answer or detailed explanation for the question", - "example": "We use a hybrid authentication system supporting both API keys and session-based authentication. API keys are used for programmatic access while sessions are used for web interface interactions." - }, - "tags": { - "description": "Tags to categorize and help search this context entry", - "example": [ - "authentication", - "security", - "api", - "sessions" - ], - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "question", - "answer" - ] - }, - "UpdateContextDto": { - "type": "object", - "properties": { - "question": { - "type": "string", - "description": "The question or topic this context entry addresses", - "example": "How do we handle user authentication in our application?" - }, - "answer": { - "type": "string", - "description": "The answer or detailed explanation for the question", - "example": "We use a hybrid authentication system supporting both API keys and session-based authentication. API keys are used for programmatic access while sessions are used for web interface interactions." - }, - "tags": { - "description": "Tags to categorize and help search this context entry", - "example": [ - "authentication", - "security", - "api", - "sessions" - ], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "FleetPolicyDto": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "Policy ID", - "example": 123 - }, - "name": { - "type": "string", - "description": "Policy name", - "example": "Password Policy" - }, - "query": { - "type": "string", - "description": "Policy query", - "example": "SELECT * FROM users;" - }, - "critical": { - "type": "boolean", - "description": "Whether policy is critical", - "example": true - }, - "description": { - "type": "string", - "description": "Policy description", - "example": "Ensures strong passwords" - }, - "author_id": { - "type": "number", - "description": "Author ID", - "example": 456 - }, - "author_name": { - "type": "string", - "description": "Author name", - "example": "John Doe" - }, - "author_email": { - "type": "string", - "description": "Author email", - "example": "john@example.com" - }, - "team_id": { - "type": "object", - "description": "Team ID", - "example": 789, - "nullable": true - }, - "resolution": { - "type": "string", - "description": "Policy resolution", - "example": "Update password settings" - }, - "platform": { - "type": "string", - "description": "Platform", - "example": "darwin" - }, - "calendar_events_enabled": { - "type": "boolean", - "description": "Calendar events enabled", - "example": false - }, - "created_at": { - "type": "string", - "description": "Created at", - "example": "2024-01-01T00:00:00Z" - }, - "updated_at": { - "type": "string", - "description": "Updated at", - "example": "2024-01-15T00:00:00Z" - }, - "response": { - "type": "string", - "description": "Policy response", - "example": "compliant" - } - }, - "required": [ - "id", - "name", - "query", - "critical", - "description", - "author_id", - "author_name", - "author_email", - "team_id", - "resolution", - "platform", - "calendar_events_enabled", - "created_at", - "updated_at", - "response" - ] - }, - "DeviceResponseDto": { - "type": "object", - "properties": { - "created_at": { - "type": "string", - "description": "Device created at", - "example": "2024-01-01T00:00:00Z" - }, - "updated_at": { - "type": "string", - "description": "Device updated at", - "example": "2024-01-15T00:00:00Z" - }, - "software": { - "type": "array", - "description": "Software list", - "items": { - "type": "object" - } - }, - "software_updated_at": { - "type": "string", - "description": "Software updated at", - "example": "2024-01-10T00:00:00Z" - }, - "id": { - "type": "number", - "description": "Device ID", - "example": 123 - }, - "detail_updated_at": { - "type": "string", - "description": "Detail updated at", - "example": "2024-01-10T00:00:00Z" - }, - "label_updated_at": { - "type": "string", - "description": "Label updated at", - "example": "2024-01-10T00:00:00Z" - }, - "policy_updated_at": { - "type": "string", - "description": "Policy updated at", - "example": "2024-01-10T00:00:00Z" - }, - "last_enrolled_at": { - "type": "string", - "description": "Last enrolled at", - "example": "2024-01-01T00:00:00Z" - }, - "seen_time": { - "type": "string", - "description": "Last seen time", - "example": "2024-01-15T12:00:00Z" - }, - "refetch_requested": { - "type": "boolean", - "description": "Refetch requested", - "example": false - }, - "hostname": { - "type": "string", - "description": "Hostname", - "example": "johns-macbook" - }, - "uuid": { - "type": "string", - "description": "Device UUID", - "example": "abc123def456" - }, - "platform": { - "type": "string", - "description": "Platform", - "example": "darwin" - }, - "osquery_version": { - "type": "string", - "description": "Osquery version", - "example": "5.10.2" - }, - "orbit_version": { - "type": "string", - "description": "Orbit version", - "example": "1.19.0" - }, - "fleet_desktop_version": { - "type": "string", - "description": "Fleet desktop version", - "example": "1.19.0" - }, - "scripts_enabled": { - "type": "boolean", - "description": "Scripts enabled", - "example": true - }, - "os_version": { - "type": "string", - "description": "OS version", - "example": "macOS 14.2.1" - }, - "build": { - "type": "string", - "description": "Build", - "example": "23C71" - }, - "platform_like": { - "type": "string", - "description": "Platform like", - "example": "darwin" - }, - "code_name": { - "type": "string", - "description": "Code name", - "example": "sonoma" - }, - "uptime": { - "type": "number", - "description": "Uptime in seconds", - "example": 86400 - }, - "memory": { - "type": "number", - "description": "Memory in bytes", - "example": 17179869184 - }, - "cpu_type": { - "type": "string", - "description": "CPU type", - "example": "x86_64" - }, - "cpu_subtype": { - "type": "string", - "description": "CPU subtype", - "example": "x86_64h" - }, - "cpu_brand": { - "type": "string", - "description": "CPU brand", - "example": "Intel(R) Core(TM) i7-9750H" - }, - "cpu_physical_cores": { - "type": "number", - "description": "CPU physical cores", - "example": 6 - }, - "cpu_logical_cores": { - "type": "number", - "description": "CPU logical cores", - "example": 12 - }, - "hardware_vendor": { - "type": "string", - "description": "Hardware vendor", - "example": "Apple Inc." - }, - "hardware_model": { - "type": "string", - "description": "Hardware model", - "example": "MacBookPro16,1" - }, - "hardware_version": { - "type": "string", - "description": "Hardware version", - "example": "1.0" - }, - "hardware_serial": { - "type": "string", - "description": "Hardware serial", - "example": "C02XW0AAJGH6" - }, - "computer_name": { - "type": "string", - "description": "Computer name", - "example": "John's MacBook Pro" - }, - "public_ip": { - "type": "string", - "description": "Public IP", - "example": "203.0.113.1" - }, - "primary_ip": { - "type": "string", - "description": "Primary IP", - "example": "192.168.1.100" - }, - "primary_mac": { - "type": "string", - "description": "Primary MAC", - "example": "00:11:22:33:44:55" - }, - "distributed_interval": { - "type": "number", - "description": "Distributed interval", - "example": 10 - }, - "config_tls_refresh": { - "type": "number", - "description": "Config TLS refresh", - "example": 3600 - }, - "logger_tls_period": { - "type": "number", - "description": "Logger TLS period", - "example": 300 - }, - "team_id": { - "type": "object", - "description": "Team ID", - "example": 1, - "nullable": true - }, - "pack_stats": { - "type": "array", - "description": "Pack stats", - "items": { - "type": "object" - } - }, - "team_name": { - "type": "object", - "description": "Team name", - "example": "Engineering", - "nullable": true - }, - "users": { - "type": "array", - "description": "Users", - "items": { - "type": "object" - } - }, - "gigs_disk_space_available": { - "type": "number", - "description": "Disk space available in GB", - "example": 250.5 - }, - "percent_disk_space_available": { - "type": "number", - "description": "Percent disk space available", - "example": 75.2 - }, - "gigs_total_disk_space": { - "type": "number", - "description": "Total disk space in GB", - "example": 500 - }, - "disk_encryption_enabled": { - "type": "boolean", - "description": "Disk encryption enabled", - "example": true - }, - "issues": { - "type": "object", - "description": "Issues", - "additionalProperties": true - }, - "mdm": { - "type": "object", - "description": "MDM info", - "additionalProperties": true - }, - "refetch_critical_queries_until": { - "type": "object", - "description": "Refetch critical queries until", - "example": "2024-01-20T00:00:00Z", - "nullable": true - }, - "last_restarted_at": { - "type": "string", - "description": "Last restarted at", - "example": "2024-01-10T08:00:00Z" - }, - "policies": { - "description": "Policies", - "type": "array", - "items": { - "$ref": "#/components/schemas/FleetPolicyDto" - } - }, - "labels": { - "type": "array", - "description": "Labels", - "items": { - "type": "object" - } - }, - "packs": { - "type": "array", - "description": "Packs", - "items": { - "type": "object" - } - }, - "batteries": { - "type": "array", - "description": "Batteries", - "items": { - "type": "object" - } - }, - "end_users": { - "type": "array", - "description": "End users", - "items": { - "type": "object" - } - }, - "last_mdm_enrolled_at": { - "type": "string", - "description": "Last MDM enrolled at", - "example": "2024-01-01T00:00:00Z" - }, - "last_mdm_checked_in_at": { - "type": "string", - "description": "Last MDM checked in at", - "example": "2024-01-15T12:00:00Z" - }, - "status": { - "type": "string", - "description": "Device status", - "example": "online" - }, - "display_text": { - "type": "string", - "description": "Display text", - "example": "Johns MacBook Pro" - }, - "display_name": { - "type": "string", - "description": "Display name", - "example": "John's MacBook Pro" - } - }, - "required": [ - "created_at", - "updated_at", - "software", - "software_updated_at", - "id", - "detail_updated_at", - "label_updated_at", - "policy_updated_at", - "last_enrolled_at", - "seen_time", - "refetch_requested", - "hostname", - "uuid", - "platform", - "osquery_version", - "orbit_version", - "fleet_desktop_version", - "scripts_enabled", - "os_version", - "build", - "platform_like", - "code_name", - "uptime", - "memory", - "cpu_type", - "cpu_subtype", - "cpu_brand", - "cpu_physical_cores", - "cpu_logical_cores", - "hardware_vendor", - "hardware_model", - "hardware_version", - "hardware_serial", - "computer_name", - "public_ip", - "primary_ip", - "primary_mac", - "distributed_interval", - "config_tls_refresh", - "logger_tls_period", - "team_id", - "pack_stats", - "team_name", - "users", - "gigs_disk_space_available", - "percent_disk_space_available", - "gigs_total_disk_space", - "disk_encryption_enabled", - "issues", - "mdm", - "refetch_critical_queries_until", - "last_restarted_at", - "policies", - "labels", - "packs", - "batteries", - "end_users", - "last_mdm_enrolled_at", - "last_mdm_checked_in_at", - "status", - "display_text", - "display_name" - ] - }, - "MemberResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Member ID", - "example": "mem_abc123def456" - }, - "userId": { - "type": "string", - "description": "User ID associated with member", - "example": "usr_abc123def456" - }, - "role": { - "type": "string", - "description": "Member role", - "example": "admin" - }, - "department": { - "type": "object", - "description": "Member department", - "example": "engineering", - "nullable": true - }, - "isActive": { - "type": "boolean", - "description": "Whether member is active", - "example": true - }, - "fleetDmLabelId": { - "type": "object", - "description": "FleetDM label ID for member devices", - "example": 123, - "nullable": true - }, - "organizationId": { - "type": "string", - "description": "Organization ID this member belongs to", - "example": "org_abc123def456" - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "When the member was created", - "example": "2024-01-01T00:00:00Z" - } - }, - "required": [ - "id", - "userId", - "role", - "department", - "isActive", - "fleetDmLabelId", - "organizationId", - "createdAt" - ] - }, - "DevicesByMemberResponseDto": { - "type": "object", - "properties": { - "data": { - "description": "Array of devices assigned to the member", - "type": "array", - "items": { - "$ref": "#/components/schemas/DeviceResponseDto" - } - }, - "count": { - "type": "number", - "description": "Total number of devices for this member", - "example": 3 - }, - "member": { - "description": "Member information", - "allOf": [ - { - "$ref": "#/components/schemas/MemberResponseDto" - } - ] - }, - "authType": { - "type": "string", - "description": "How the request was authenticated", - "enum": [ - "api-key", - "session" - ], - "example": "api-key" - }, - "authenticatedUser": { - "type": "object", - "description": "Authenticated user information (present for session auth)", - "example": { - "id": "usr_abc123def456", - "email": "user@company.com" - } - } - }, - "required": [ - "data", - "count", - "member", - "authType" - ] - }, - "PolicyResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The policy ID", - "example": "pol_abc123def456" - }, - "name": { - "type": "string", - "description": "Name of the policy", - "example": "Data Privacy Policy" - }, - "description": { - "type": "string", - "description": "Description of the policy", - "example": "This policy outlines how we handle and protect personal data", - "nullable": true - }, - "status": { - "type": "string", - "description": "Status of the policy", - "enum": [ - "draft", - "published", - "needs_review" - ], - "example": "draft" - }, - "content": { - "type": "array", - "description": "Content of the policy as TipTap JSON (array of nodes)", - "example": [ - { - "type": "heading", - "attrs": { - "level": 2, - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Purpose" - } - ] - }, - { - "type": "paragraph", - "attrs": { - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Verify workforce integrity and grant the right access at start, revoke at end." - } - ] - } - ], - "items": { - "type": "object", - "additionalProperties": true - } - }, - "frequency": { - "type": "string", - "description": "Review frequency of the policy", - "enum": [ - "monthly", - "quarterly", - "yearly" - ], - "example": "yearly", - "nullable": true - }, - "department": { - "type": "string", - "description": "Department this policy applies to", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it", - "nullable": true - }, - "isRequiredToSign": { - "type": "boolean", - "description": "Whether this policy requires a signature", - "example": true - }, - "signedBy": { - "type": "array", - "description": "List of user IDs who have signed this policy", - "example": [ - "usr_123", - "usr_456" - ], - "items": { - "type": "string" - } - }, - "reviewDate": { - "format": "date-time", - "type": "string", - "description": "Review date for the policy", - "example": "2024-12-31T00:00:00.000Z", - "nullable": true - }, - "isArchived": { - "type": "boolean", - "description": "Whether this policy is archived", - "example": false - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "When the policy was created", - "example": "2024-01-01T00:00:00.000Z" - }, - "updatedAt": { - "format": "date-time", - "type": "string", - "description": "When the policy was last updated", - "example": "2024-01-15T00:00:00.000Z" - }, - "lastArchivedAt": { - "format": "date-time", - "type": "string", - "description": "When the policy was last archived", - "example": "2024-02-01T00:00:00.000Z", - "nullable": true - }, - "lastPublishedAt": { - "format": "date-time", - "type": "string", - "description": "When the policy was last published", - "example": "2024-01-10T00:00:00.000Z", - "nullable": true - }, - "organizationId": { - "type": "string", - "description": "Organization ID this policy belongs to", - "example": "org_abc123def456" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to this policy", - "example": "usr_abc123def456", - "nullable": true - }, - "approverId": { - "type": "string", - "description": "ID of the user who approved this policy", - "example": "usr_xyz789abc123", - "nullable": true - }, - "policyTemplateId": { - "type": "string", - "description": "ID of the policy template this policy is based on", - "example": "plt_template123", - "nullable": true - } - }, - "required": [ - "id", - "name", - "description", - "status", - "content", - "frequency", - "department", - "isRequiredToSign", - "signedBy", - "reviewDate", - "isArchived", - "createdAt", - "updatedAt", - "lastArchivedAt", - "lastPublishedAt", - "organizationId", - "assigneeId", - "approverId", - "policyTemplateId" - ] - }, - "CreatePolicyDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the policy", - "example": "Data Privacy Policy" - }, - "description": { - "type": "string", - "description": "Description of the policy", - "example": "This policy outlines how we handle and protect personal data" - }, - "status": { - "type": "string", - "description": "Status of the policy", - "enum": [ - "draft", - "published", - "needs_review" - ], - "example": "draft" - }, - "content": { - "type": "array", - "description": "Content of the policy as TipTap JSON (array of nodes)", - "example": [ - { - "type": "heading", - "attrs": { - "level": 2, - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Purpose" - } - ] - }, - { - "type": "paragraph", - "attrs": { - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Verify workforce integrity and grant the right access at start, revoke at end." - } - ] - } - ], - "items": { - "type": "object", - "additionalProperties": true - } - }, - "frequency": { - "type": "string", - "description": "Review frequency of the policy", - "enum": [ - "monthly", - "quarterly", - "yearly" - ], - "example": "yearly" - }, - "department": { - "type": "string", - "description": "Department this policy applies to", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "isRequiredToSign": { - "type": "boolean", - "description": "Whether this policy requires a signature", - "example": true - }, - "reviewDate": { - "type": "string", - "description": "Review date for the policy", - "example": "2024-12-31T00:00:00.000Z" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to this policy", - "example": "usr_abc123def456" - }, - "approverId": { - "type": "string", - "description": "ID of the user who approved this policy", - "example": "usr_xyz789abc123" - }, - "policyTemplateId": { - "type": "string", - "description": "ID of the policy template this policy is based on", - "example": "plt_template123" - }, - "signedBy": { - "type": "array", - "description": "List of user IDs who have signed this policy", - "example": [ - "usr_123", - "usr_456" - ], - "items": { - "type": "string" - } - } - }, - "required": [ - "name", - "content" - ] - }, - "UpdatePolicyDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the policy", - "example": "Data Privacy Policy" - }, - "description": { - "type": "string", - "description": "Description of the policy", - "example": "This policy outlines how we handle and protect personal data" - }, - "status": { - "type": "string", - "description": "Status of the policy", - "enum": [ - "draft", - "published", - "needs_review" - ], - "example": "draft" - }, - "content": { - "type": "array", - "description": "Content of the policy as TipTap JSON (array of nodes)", - "example": [ - { - "type": "heading", - "attrs": { - "level": 2, - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Purpose" - } - ] - }, - { - "type": "paragraph", - "attrs": { - "textAlign": null - }, - "content": [ - { - "type": "text", - "text": "Verify workforce integrity and grant the right access at start, revoke at end." - } - ] - } - ], - "items": { - "type": "object", - "additionalProperties": true - } - }, - "frequency": { - "type": "string", - "description": "Review frequency of the policy", - "enum": [ - "monthly", - "quarterly", - "yearly" - ], - "example": "yearly" - }, - "department": { - "type": "string", - "description": "Department this policy applies to", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - }, - "isRequiredToSign": { - "type": "boolean", - "description": "Whether this policy requires a signature", - "example": true - }, - "reviewDate": { - "type": "string", - "description": "Review date for the policy", - "example": "2024-12-31T00:00:00.000Z" - }, - "assigneeId": { - "type": "string", - "description": "ID of the user assigned to this policy", - "example": "usr_abc123def456" - }, - "approverId": { - "type": "string", - "description": "ID of the user who approved this policy", - "example": "usr_xyz789abc123" - }, - "policyTemplateId": { - "type": "string", - "description": "ID of the policy template this policy is based on", - "example": "plt_template123" - }, - "signedBy": { - "type": "array", - "description": "List of user IDs who have signed this policy", - "example": [ - "usr_123", - "usr_456" - ], - "items": { - "type": "string" - } - }, - "isArchived": { - "type": "boolean", - "description": "Whether to archive this policy", - "example": false - } - } - }, - "AISuggestPolicyRequestDto": { - "type": "object", - "properties": { - "instructions": { - "type": "string", - "description": "User instructions about what changes to make to the policy", - "example": "Update the data retention section to specify a 7-year retention period" - }, - "chatHistory": { - "type": "array", - "description": "Chat history for context (array of messages with role and content)", - "example": [ - { - "role": "user", - "content": "Update the data retention policy" - }, - { - "role": "assistant", - "content": "I can help with that..." - } - ], - "items": { - "type": "object", - "properties": { - "role": { - "type": "string", - "enum": [ - "user", - "assistant" - ] - }, - "content": { - "type": "string" - } - } - } - } - }, - "required": [ - "instructions" - ] - }, - "TaskResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Unique identifier for the task", - "example": "tsk_abc123def456" - }, - "title": { - "type": "string", - "description": "Task title", - "example": "Implement user authentication" - }, - "description": { - "type": "string", - "description": "Task description", - "example": "Add OAuth 2.0 authentication to the platform" - }, - "status": { - "type": "string", - "description": "Task status", - "example": "in_progress", - "enum": [ - "todo", - "in_progress", - "done", - "blocked" - ] - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "Task creation timestamp", - "example": "2024-01-15T10:30:00Z" - }, - "updatedAt": { - "format": "date-time", - "type": "string", - "description": "Task last update timestamp", - "example": "2024-01-15T10:30:00Z" - }, - "taskTemplateId": { - "type": "object", - "description": "Task template ID", - "example": "frk_tt_68406e353df3bc002994acef", - "nullable": true - } - }, - "required": [ - "id", - "title", - "status", - "createdAt", - "updatedAt" - ] - }, - "AttachmentResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Unique identifier for the attachment", - "example": "att_abc123def456" - }, - "name": { - "type": "string", - "description": "Original filename", - "example": "document.pdf" - }, - "type": { - "type": "string", - "description": "File type/MIME type", - "example": "application/pdf" - }, - "size": { - "type": "number", - "description": "File size in bytes", - "example": 1024000 - }, - "downloadUrl": { - "type": "string", - "description": "Signed URL for downloading the file (temporary)", - "example": "https://bucket.s3.amazonaws.com/path/to/file.pdf?signature=..." - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "Upload timestamp", - "example": "2024-01-15T10:30:00Z" - } - }, - "required": [ - "id", - "name", - "type", - "size", - "downloadUrl", - "createdAt" - ] - }, - "UploadAttachmentDto": { - "type": "object", - "properties": { - "fileName": { - "type": "string", - "description": "Name of the file", - "example": "document.pdf", - "maxLength": 255 - }, - "fileType": { - "type": "string", - "description": "MIME type of the file", - "example": "application/pdf" - }, - "fileData": { - "type": "string", - "description": "Base64 encoded file data", - "example": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" - }, - "description": { - "type": "string", - "description": "Description of the attachment", - "example": "Meeting notes from Q4 planning session", - "maxLength": 500 - } - }, - "required": [ - "fileName", - "fileType", - "fileData" - ] - }, - "UpdateAutomationDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Automation name", - "example": "GitHub Security Check - Evidence Collection" - }, - "description": { - "type": "string", - "description": "Automation description", - "example": "Collects evidence about GitHub repository security settings" - } - } - }, - "AuthorResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "User ID", - "example": "usr_abc123def456" - }, - "name": { - "type": "string", - "description": "User name", - "example": "John Doe" - }, - "email": { - "type": "string", - "description": "User email", - "example": "john.doe@company.com" - }, - "image": { - "type": "object", - "description": "User profile image URL", - "example": "https://example.com/avatar.jpg", - "nullable": true - }, - "deactivated": { - "type": "boolean", - "description": "Whether the user is deactivated", - "example": false, - "nullable": true - } - }, - "required": [ - "id", - "name", - "email", - "image", - "deactivated" - ] - }, - "AttachmentMetadataDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Unique identifier for the attachment", - "example": "att_abc123def456" - }, - "name": { - "type": "string", - "description": "Original filename", - "example": "document.pdf" - }, - "type": { - "type": "string", - "description": "File type/MIME type", - "example": "application/pdf" - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "Upload timestamp", - "example": "2024-01-15T10:30:00Z" - } - }, - "required": [ - "id", - "name", - "type", - "createdAt" - ] - }, - "CommentResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Unique identifier for the comment", - "example": "cmt_abc123def456" - }, - "content": { - "type": "string", - "description": "Comment content", - "example": "This task needs to be completed by end of week" - }, - "author": { - "description": "Comment author information", - "allOf": [ - { - "$ref": "#/components/schemas/AuthorResponseDto" - } - ] - }, - "attachments": { - "description": "Attachment metadata (URLs generated on-demand)", - "type": "array", - "items": { - "$ref": "#/components/schemas/AttachmentMetadataDto" - } - }, - "createdAt": { - "format": "date-time", - "type": "string", - "description": "Comment creation timestamp", - "example": "2024-01-15T10:30:00Z" - } - }, - "required": [ - "id", - "content", - "author", - "attachments", - "createdAt" - ] - }, - "CreateCommentDto": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the comment", - "example": "This task needs to be completed by end of week", - "maxLength": 2000 - }, - "entityId": { - "type": "string", - "description": "ID of the entity to comment on", - "example": "tsk_abc123def456" - }, - "entityType": { - "type": "string", - "description": "Type of entity being commented on", - "enum": [ - "task", - "vendor", - "risk", - "policy" - ], - "example": "task" - }, - "attachments": { - "description": "Optional attachments to include with the comment", - "type": "array", - "items": { - "$ref": "#/components/schemas/UploadAttachmentDto" - } - } - }, - "required": [ - "content", - "entityId", - "entityType" - ] - }, - "UpdateCommentDto": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Updated content of the comment", - "example": "This task needs to be completed by end of week (updated)", - "maxLength": 2000 - } - }, - "required": [ - "content" - ] - }, - "DomainVerificationDto": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Verification type (e.g., TXT, CNAME)" - }, - "domain": { - "type": "string", - "description": "Domain for verification" - }, - "value": { - "type": "string", - "description": "Verification value" - }, - "reason": { - "type": "string", - "description": "Reason for verification status" - } - }, - "required": [ - "type", - "domain", - "value" - ] - }, - "DomainStatusResponseDto": { - "type": "object", - "properties": { - "domain": { - "type": "string", - "description": "The domain name" - }, - "verified": { - "type": "boolean", - "description": "Whether the domain is verified" - }, - "verification": { - "description": "Verification records for the domain", - "type": "array", - "items": { - "$ref": "#/components/schemas/DomainVerificationDto" - } - } - }, - "required": [ - "domain", - "verified" - ] - }, - "CreateAccessRequestDto": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "email": { - "type": "string" - }, - "company": { - "type": "string" - }, - "jobTitle": { - "type": "string" - }, - "purpose": { - "type": "string" - }, - "requestedDurationDays": { - "type": "number", - "minimum": 1 - } - }, - "required": [ - "name", - "email" - ] - }, - "ApproveAccessRequestDto": { - "type": "object", - "properties": { - "durationDays": { - "type": "number", - "minimum": 1 - } - } - }, - "DenyAccessRequestDto": { - "type": "object", - "properties": { - "reason": { - "type": "string" - } - }, - "required": [ - "reason" - ] - }, - "RevokeGrantDto": { - "type": "object", - "properties": { - "reason": { - "type": "string" - } - }, - "required": [ - "reason" - ] - }, - "SignNdaDto": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "email": { - "type": "string" - }, - "accept": { - "type": "boolean" - } - }, - "required": [ - "name", - "email", - "accept" - ] - }, - "ReclaimAccessDto": { - "type": "object", - "properties": { - "email": { - "type": "string" - } - }, - "required": [ - "email" - ] - }, - "UpdateTaskTemplateDto": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Task template name", - "example": "Monthly Security Review" - }, - "description": { - "type": "string", - "description": "Detailed description of the task template", - "example": "Review and update security policies on a monthly basis" - }, - "frequency": { - "type": "string", - "description": "Frequency of the task", - "enum": [ - "monthly", - "quarterly", - "yearly" - ], - "example": "monthly" - }, - "department": { - "type": "string", - "description": "Department responsible for the task", - "enum": [ - "none", - "admin", - "gov", - "hr", - "it", - "itsm", - "qms" - ], - "example": "it" - } - } - } - } - } -} \ No newline at end of file diff --git a/apps/api/src/gen-openapi.spec.ts b/apps/api/src/gen-openapi.spec.ts index 423ea2bc83..9c1e961c08 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,13 @@ 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'; +import { collectPublicOpenApiIssues } from './openapi/public-docs-quality'; const shouldRun = process.env.GEN_OPENAPI === '1'; const maybeDescribe = shouldRun ? describe : describe.skip; @@ -121,18 +134,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 +147,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 +165,16 @@ maybeDescribe('Generate openapi.json', () => { writeFileSync(openapiPath, JSON.stringify(document, null, 2)); console.log(`OpenAPI documentation written to ${openapiPath}`); - // Verify excluded paths are absent - const hiddenPrefixes = ['/v1/auth', '/v1/admin', '/v1/internal']; - for (const prefix of hiddenPrefixes) { - const exposed = Object.keys(document.paths).filter((p) => - p.startsWith(prefix), - ); - expect(exposed).toEqual([]); - } + expect(document.servers).toEqual([ + { + url: PUBLIC_SERVER_URL, + description: 'Production API Server', + }, + ]); + + const issues = collectPublicOpenApiIssues(document); + expect(issues.excludedPaths).toEqual([]); + expect(issues.exposedTags).toEqual([]); + expect(issues.sensitiveSchemaDetails).toEqual([]); }); }); diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index d46f1dd99c..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,8 +158,8 @@ async function bootstrap(): Promise { const serverDescription = describeServer(baseUrl); 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( { @@ -169,6 +174,8 @@ async function bootstrap(): Promise { .build(); const document: OpenAPIObject = SwaggerModule.createDocument(app, config); + applyPublicOpenApiMetadata(document); + // Setup Swagger UI at /api/docs SwaggerModule.setup('api/docs', app, document, { raw: ['json'], diff --git a/apps/api/src/openapi-docs.spec.ts b/apps/api/src/openapi-docs.spec.ts index 6317293e92..da66189b30 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,20 @@ 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'; +import { collectPublicOpenApiIssues } from './openapi/public-docs-quality'; describe('OpenAPI document', () => { let app: INestApplication; @@ -119,37 +132,73 @@ 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']; + 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', + }, + ]); + }); + + it('keeps the public spec complete, SEO-ready, and free of private surfaces', () => { + const issues = collectPublicOpenApiIssues(document); - 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([]); + expect(issues.excludedPaths).toEqual([]); + expect(issues.exposedTags).toEqual([]); + expect(issues.invalidSeo).toEqual([]); + expect(issues.missingMetadata).toEqual([]); + expect(issues.missingSummaries).toEqual([]); + expect(issues.sensitiveSchemaDetails).toEqual([]); }); - } - 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)) { - if (typeof op !== 'object' || !op) continue; - if (!op.summary || op.summary.trim() === '') { - missing.push(`${method.toUpperCase()} ${routePath}`); + it('curates high-value API pages with operation-specific SEO copy', () => { + expect( + document.paths['/v1/questionnaire/parse/upload/token'], + ).toBeUndefined(); + + const upload = document.paths['/v1/questionnaire/parse/upload']?.post as + | { + summary?: string; + description?: string; + 'x-mint'?: { href?: string; metadata?: { title?: string } }; + } + | undefined; + + expect(upload?.summary).toBe('Auto-answer uploaded questionnaire'); + expect(upload?.description).toContain('approved organization evidence'); + expect(upload?.['x-mint']?.href).toBe( + '/api-reference/questionnaire/upload-a-questionnaire-file-and-auto-answer-with-export', + ); + + const policies = document.paths['/v1/policies']?.get as + | { + summary?: string; + description?: string; + 'x-mint'?: { metadata?: { title?: string } }; } - } - } - expect(missing).toEqual([]); + | 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..8e8820e2fb --- /dev/null +++ b/apps/api/src/openapi/operation-metadata.ts @@ -0,0 +1,271 @@ +import { QUESTIONNAIRE_OPERATION_METADATA } from './questionnaire-metadata'; +import { TRUST_OPERATION_METADATA } from './trust-operation-metadata'; +import type { PublicOperationMetadata } from './types'; +import { WORKFLOW_OPERATION_METADATA } from './workflow-operation-metadata'; + +const CORE_OPERATION_METADATA: Record = { + AttachmentsController_getAttachmentDownloadUrl_v1: { + summary: 'Get shared attachment download URL', + description: + 'Generate a signed download URL for a shared attachment linked to comments, evidence records, or compliance workflow reviews.', + }, + 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_getPrimaryColor_v1: { + summary: 'Get organization brand color', + description: + 'Retrieve the organization primary brand color used for Trust Center theming, portals, and API-driven embedded experiences.', + }, + 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.', + }, + PoliciesController_getPolicy_v1: { + summary: 'Get compliance policy', + description: + 'Retrieve one compliance policy with its current content, review status, framework links, and audit-ready metadata.', + }, + PoliciesController_updatePolicy_v1: { + summary: 'Update compliance policy', + description: + 'Update compliance policy details or content while keeping policy workflows connected to controls, tasks, and approvals.', + }, + PoliciesController_deletePolicy_v1: { + summary: 'Delete compliance policy', + description: + 'Delete a compliance policy that is no longer part of the organization evidence library or control program.', + }, + 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.', + }, + TasksController_getTaskAttachmentDownloadUrl_v1: { + summary: 'Get task attachment download URL', + description: + 'Generate a signed download URL for an attachment on a compliance task so reviewers can access uploaded evidence.', + }, + 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.', + }, + ContextController_getAllContext_v1: { + summary: 'List organization context', + description: + 'List organization context entries used as approved source material for evidence, questionnaires, policies, and AI workflows.', + }, + ContextController_getContextById_v1: { + summary: 'Get organization context', + description: + 'Retrieve one organization context entry with source details and approved content for compliance automation workflows.', + }, + ContextController_updateContext_v1: { + summary: 'Update organization context', + description: + 'Update an organization context entry so approved business details stay current for evidence and questionnaire automation.', + }, + 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.', + }, + DevicesController_getAllDevices_v1: { + summary: 'List managed devices', + description: + 'List managed employee devices with endpoint compliance status, ownership, and security check results for workforce 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.', + }, + PeopleController_updateMember_v1: { + summary: 'Update workforce member', + description: + 'Update a workforce member profile, role, department, or compliance metadata used for people-security controls.', + }, + 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.', + }, + VendorsController_getVendorById_v1: { + summary: 'Get vendor details', + description: + 'Retrieve one vendor record with ownership, review status, risk context, and third-party compliance metadata.', + }, + VendorsController_updateVendor_v1: { + summary: 'Update vendor record', + description: + 'Update vendor ownership, risk attributes, review metadata, and third-party compliance context for an organization.', + }, + RisksController_getRiskById_v1: { + summary: 'Get organization risk', + description: + 'Retrieve one organization risk with owner, department, likelihood, impact, mitigation, and remediation context.', + }, + RisksController_deleteRisk_v1: { + summary: 'Delete organization risk', + description: + 'Delete an organization risk that no longer needs active tracking in the risk register or compliance program.', + }, + 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, + ...TRUST_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 new file mode 100644 index 0000000000..28315c90e3 --- /dev/null +++ b/apps/api/src/openapi/public-docs-metadata.ts @@ -0,0 +1,237 @@ +import type { OpenAPIObject } from '@nestjs/swagger'; +import { PUBLIC_OPERATION_METADATA } from './operation-metadata'; +import { + PUBLIC_DOCS_EXCLUDED_PATH_PATTERNS, + PUBLIC_DOCS_EXCLUDED_PREFIXES, +} from './public-docs-quality'; +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 = + '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'; + +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; + } + + if (metadata.security) { + operation.security = metadata.security; + } +} + +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)) { + const isExcluded = + PUBLIC_DOCS_EXCLUDED_PREFIXES.some((prefix) => + routePath.startsWith(prefix), + ) || + PUBLIC_DOCS_EXCLUDED_PATH_PATTERNS.some((pattern) => + pattern.test(routePath), + ); + + if (isExcluded) { + 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, methods] of Object.entries(paths)) { + for (const [method, operation] of Object.entries(methods)) { + if (!operation || typeof operation !== 'object') { + continue; + } + + const metadata = operation.operationId + ? PUBLIC_OPERATION_METADATA[operation.operationId] + : undefined; + + if (metadata) { + applyOperationMetadata(operation, metadata); + } + + const visibility = getVisibilityForOperation(operation, metadata); + if (visibility === 'excluded') { + delete methods[method]; + continue; + } + + 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/public-docs-quality.ts b/apps/api/src/openapi/public-docs-quality.ts new file mode 100644 index 0000000000..bf1ee5f50e --- /dev/null +++ b/apps/api/src/openapi/public-docs-quality.ts @@ -0,0 +1,120 @@ +import type { OpenAPIObject } from '@nestjs/swagger'; + +export const PUBLIC_DOCS_EXCLUDED_PREFIXES = [ + '/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/background-checks', + '/v1/pentest-credits', + '/v1/finding-template', + '/v1/integrations/oauth', + '/v1/integrations/oauth-apps', + '/v1/cloud-security/legacy', + '/v1/cloud-security/remediation', + '/v1/questionnaire/parse/upload/token', + '/v1/trust-access/access', + '/v1/trust-access/nda', +]; + +export const PUBLIC_DOCS_EXCLUDED_PATH_PATTERNS = [ + /\/background-check(?:\/|$)/, + /\/credentials(?:\/|$)/, + /\/ensure-valid-credentials(?:\/|$)/, + /\/webhooks?(?:\/|$)/, +]; + +const SENSITIVE_TAGS = [ + 'Background Check Billing', + 'Background Checks', + 'Billing', + 'Finding Templates', + 'Pentest Credits', + 'Secrets', +]; + +const SENSITIVE_SCHEMA_DETAILS = [ + 'BackgroundCheckBillingPortalDto', + 'BillingPortalDto', + 'BillingPreferencesDto', + 'CreateFindingTemplateDto', + 'Finding template ID', + 'Maced check IDs', + 'UpdateFindingTemplateDto', + 'impact_proof', + 'pipelineTesting', + 'secrets_info_disclosure', + 'testMode', + 'webhookUrl', +]; + +type OperationForQuality = { + description?: string; + summary?: string; + tags?: string[]; + 'x-excluded'?: true; + 'x-mint'?: { + metadata?: { description?: string; title?: string }; + }; +}; + +export function collectPublicOpenApiIssues(document: OpenAPIObject) { + const excludedPaths = Object.keys(document.paths).filter( + (path) => + PUBLIC_DOCS_EXCLUDED_PREFIXES.some((prefix) => path.startsWith(prefix)) || + PUBLIC_DOCS_EXCLUDED_PATH_PATTERNS.some((pattern) => pattern.test(path)), + ); + const missingSummaries: string[] = []; + const missingMetadata: string[] = []; + const invalidSeo: string[] = []; + const exposedTags = new Set(); + + for (const [routePath, methods] of Object.entries(document.paths)) { + for (const [method, op] of Object.entries( + methods as Record, + )) { + if (typeof op !== 'object' || !op || op['x-excluded']) continue; + const endpoint = `${method.toUpperCase()} ${routePath}`; + const meta = op['x-mint']?.metadata; + + if (!op.summary?.trim()) missingSummaries.push(endpoint); + if (!op.description?.trim() || !meta?.description?.trim()) { + missingMetadata.push(endpoint); + } + if ( + meta?.description && + (meta.description.length < 80 || + meta.description.length > 160 || + meta.description.includes('Use this Comp AI')) + ) { + invalidSeo.push(endpoint); + } + if (meta?.title && meta.title.length > 60) invalidSeo.push(endpoint); + for (const tag of op.tags ?? []) { + if (SENSITIVE_TAGS.includes(tag)) exposedTags.add(tag); + } + } + } + + const serialized = JSON.stringify(document); + const sensitiveSchemaDetails = SENSITIVE_SCHEMA_DETAILS.filter((detail) => + serialized.includes(detail), + ); + + return { + excludedPaths, + exposedTags: [...exposedTags].sort(), + invalidSeo, + missingMetadata, + missingSummaries, + sensitiveSchemaDetails, + }; +} diff --git a/apps/api/src/openapi/questionnaire-metadata.ts b/apps/api/src/openapi/questionnaire-metadata.ts new file mode 100644 index 0000000000..adfbac0ea6 --- /dev/null +++ b/apps/api/src/openapi/questionnaire-metadata.ts @@ -0,0 +1,97 @@ +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: + 'Internal Trust Access questionnaire upload used by the Comp AI frontend for reviewer access sessions.', + visibility: 'excluded', + }, + 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..631b00b19f --- /dev/null +++ b/apps/api/src/openapi/schema-pruning.ts @@ -0,0 +1,126 @@ +import type { OpenAPIObject } from '@nestjs/swagger'; + +const INTERNAL_PROPERTY_NAMES = [ + 'checks', + 'evidenceLevel', + 'pipelineTesting', + 'scanDepth', + 'source', + 'testMode', + 'webhookUrl', +]; + +function shouldRemoveProperty(propertyName: string, propertySchema: unknown) { + if (INTERNAL_PROPERTY_NAMES.includes(propertyName)) { + return true; + } + + if (propertyName !== 'templateId') { + return false; + } + + return JSON.stringify(propertySchema).includes('Finding template'); +} + +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; + const properties = record.properties; + + if (properties && typeof properties === 'object') { + const schemaProperties = properties as Record; + const removedProperties = new Set(); + for (const [propertyName, propertySchema] of Object.entries( + schemaProperties, + )) { + if (shouldRemoveProperty(propertyName, propertySchema)) { + removedProperties.add(propertyName); + delete schemaProperties[propertyName]; + } + } + + if (Array.isArray(record.required)) { + record.required = record.required.filter( + (propertyName) => + typeof propertyName !== 'string' || + !removedProperties.has(propertyName), + ); + } + } + + 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..25ba9a9f80 --- /dev/null +++ b/apps/api/src/openapi/seo-text.ts @@ -0,0 +1,80 @@ +const TRAILING_FRAGMENT_WORDS = [ + 'and', + 'bearer', + 'by', + 'for', + 'from', + 'or', + 'session', + 'to', + 'via', + 'with', +]; + +function removeAuthBoilerplate(value: string): string { + return value + .replace(/Supports both API key authentication[^.]*\./gi, '') + .replace(/Supports API key authentication[^.]*\./gi, '') + .replace(/Supports session authentication[^.]*\./gi, ''); +} + +function toSentence(value: string): string { + const normalized = removeAuthBoilerplate(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(' '); + let safe = boundary > 80 ? truncated.slice(0, boundary) : truncated; + while ( + TRAILING_FRAGMENT_WORDS.some((word) => + new RegExp(`\\b${word}$`, 'i').test(safe), + ) + ) { + safe = safe.slice(0, safe.lastIndexOf(' ')).trim(); + } + 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..084973cd96 --- /dev/null +++ b/apps/api/src/openapi/tag-metadata.ts @@ -0,0 +1,191 @@ +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.', + visibility: 'excluded', + }, + 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, 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/trust-operation-metadata.ts b/apps/api/src/openapi/trust-operation-metadata.ts new file mode 100644 index 0000000000..ce73567df9 --- /dev/null +++ b/apps/api/src/openapi/trust-operation-metadata.ts @@ -0,0 +1,136 @@ +import type { PublicOperationMetadata } from './types'; + +export const TRUST_OPERATION_METADATA: Record = + { + TrustAccessController_createAccessRequest_v1: { + summary: 'Submit Trust Access request', + description: + 'Submit a Trust Center access request with requester details, company context, and review reason for administrator approval.', + }, + TrustAccessController_listAccessRequests_v1: { + summary: 'List Trust Access requests', + description: + 'List pending and completed Trust Center access requests so teams can review customer security inquiries through the API.', + }, + TrustAccessController_getAccessRequest_v1: { + summary: 'Get Trust Access request', + description: + 'Retrieve one Trust Center access request with requester context, status, review metadata, and audit details.', + }, + 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_denyRequest_v1: { + summary: 'Deny Trust Access request', + description: + 'Reject a Trust Center access request with a review reason so customer security access decisions stay auditable.', + }, + TrustAccessController_listGrants_v1: { + summary: 'List Trust Access grants', + description: + 'List active, expired, and revoked Trust Access grants for customer security reviews and shared compliance resources.', + }, + TrustAccessController_revokeGrant_v1: { + summary: 'Revoke Trust Access grant', + description: + 'Immediately revoke a Trust Access grant when a customer review ends or shared compliance access should be removed.', + }, + TrustAccessController_resendAccessEmail_v1: { + summary: 'Resend Trust Access email', + description: + 'Resend the access email for an active Trust Access grant so approved reviewers can reopen shared resources.', + }, + TrustAccessController_resendNda_v1: { + summary: 'Resend Trust Access NDA', + description: + 'Resend an NDA signing email for a Trust Access request that still requires reviewer signature.', + }, + TrustAccessController_previewNda_v1: { + summary: 'Preview Trust Access NDA', + description: + 'Generate a preview NDA PDF for a Trust Access request before the reviewer signs and receives access.', + }, + TrustAccessController_reclaimAccess_v1: { + summary: 'Reclaim Trust Access link', + description: + 'Request a fresh Trust Access link for a reviewer who already has an active grant on a published Trust Center.', + }, + TrustAccessController_getNda_v1: { + summary: 'Get Trust Access NDA', + description: + 'Internal Trust Portal NDA session endpoint used by the Comp AI frontend during reviewer access flows.', + visibility: 'excluded', + }, + TrustAccessController_previewNdaByToken_v1: { + summary: 'Preview Trust Access NDA by session', + description: + 'Internal Trust Portal NDA preview endpoint used by the Comp AI frontend during reviewer access flows.', + visibility: 'excluded', + }, + TrustAccessController_signNda_v1: { + summary: 'Sign Trust Access NDA', + description: + 'Internal Trust Portal NDA signing endpoint used by the Comp AI frontend during reviewer access flows.', + visibility: 'excluded', + }, + TrustAccessController_getGrantByAccessToken_v1: { + summary: 'Get Trust Access grant session', + description: + 'Internal Trust Portal grant session endpoint used by the Comp AI frontend for reviewer access.', + visibility: 'excluded', + }, + TrustAccessController_getPoliciesByAccessToken_v1: { + summary: 'List Trust Access policies by session', + description: + 'Internal Trust Portal policy session endpoint used by the Comp AI frontend for reviewer access.', + visibility: 'excluded', + }, + TrustAccessController_downloadAllPolicies_v1: { + summary: 'Download Trust Access policy bundle', + description: + 'Internal Trust Portal policy bundle endpoint used by the Comp AI frontend for reviewer access.', + visibility: 'excluded', + }, + TrustAccessController_getFaqs_v1: { + summary: 'Get Trust Center FAQs', + description: + 'Retrieve published Trust Center FAQs for an organization so public trust pages can show customer security answers.', + }, + TrustAccessController_getPublicOverview_v1: { + summary: 'Get Trust Center overview', + description: + 'Retrieve the published Trust Center overview for an organization, including public security posture messaging.', + }, + TrustAccessController_getPublicCustomLinks_v1: { + summary: 'List Trust Center custom links', + description: + 'List published custom links shown on an organization Trust Center for customer security and compliance reviews.', + }, + TrustAccessController_getPublicFavicon_v1: { + summary: 'Get Trust Center favicon', + description: + 'Retrieve the favicon URL used by a published Trust Center so embedded or mirrored experiences can match branding.', + }, + TrustAccessController_getPublicVendors_v1: { + summary: 'List Trust Center vendors', + description: + 'List published vendors and subprocessors for an organization Trust Center so reviewers can inspect third-party posture.', + }, + 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.', + }, + }; diff --git a/apps/api/src/openapi/types.ts b/apps/api/src/openapi/types.ts new file mode 100644 index 0000000000..fd2ef08b80 --- /dev/null +++ b/apps/api/src/openapi/types.ts @@ -0,0 +1,30 @@ +export type PublicVisibility = 'public' | 'hidden' | 'excluded'; + +export type PublicOperationMetadata = { + summary: string; + description: string; + href?: string; + sidebarTitle?: string; + visibility?: PublicVisibility; + content?: string; + security?: Array>; + 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..d1d09571d1 --- /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, 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 new file mode 100644 index 0000000000..dd7b89d8ba --- /dev/null +++ b/packages/docs/api-reference/overview.mdx @@ -0,0 +1,96 @@ +--- +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 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 custom framework programs connected to the systems where work actually happens. + +## Authentication + +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 --request GET \ + --url "https://api.trycomp.ai/v1/organization" \ + --header "X-API-Key: $COMP_AI_API_KEY" +``` + +The reference covers Comp AI's product APIs across compliance operations, not just a single workflow. API-key backed endpoints are the integration contract for organization automation, while app session routes are kept out of the public reference. + +- Trust Access management, public Trust Center data, evidence, policies, questionnaires, vendors, risks, integrations, cloud checks, and device compliance are API-backed workflows. +- 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, platform-admin, 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 + + + + Connect tasks to recurring evidence collection so audit proof stays current. + + + + Upload questionnaires, generate approved answers, and export reviewer-ready files. + + + + Draft, version, publish, and export policies that support audits and customer reviews. + + + + Approve external reviewers, collect NDAs, and share controlled Trust Center resources. + + + + 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. + + + +## 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. + +## Implementation Guidance + +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: + +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. + +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 -