openapi: 3.1.0 info: title: StellaOps Excititor Chunk API version: 1.0.0 description: | API for VEX document chunked ingestion and processing in Excititor service. Unblocks EXCITITOR-DOCS-0001, EXCITITOR-ENG-0001, EXCITITOR-OPS-0001 (3 tasks). contact: name: StellaOps Platform Team url: https://stella-ops.org license: name: AGPL-3.0-or-later url: https://www.gnu.org/licenses/agpl-3.0.html servers: - url: /api/v1/excititor description: Excititor API base path tags: - name: chunks description: Chunked document upload operations - name: vex description: VEX document ingestion - name: processing description: Document processing status - name: health description: Service health endpoints paths: /chunks/initiate: post: operationId: initiateChunkedUpload summary: Initiate a chunked upload session description: Start a new chunked upload session for large VEX documents tags: - chunks security: - bearerAuth: [] - oauth2: [excititor:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChunkedUploadInitRequest' responses: '201': description: Upload session created content: application/json: schema: $ref: '#/components/schemas/ChunkedUploadSession' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /chunks/{session_id}: put: operationId: uploadChunk summary: Upload a chunk description: Upload a single chunk for an active upload session tags: - chunks security: - bearerAuth: [] - oauth2: [excititor:write] parameters: - name: session_id in: path required: true schema: type: string format: uuid - name: X-Chunk-Index in: header required: true schema: type: integer minimum: 0 - name: X-Chunk-Digest in: header required: true schema: type: string pattern: '^sha256:[a-f0-9]{64}$' - name: Content-Range in: header required: false schema: type: string requestBody: required: true content: application/octet-stream: schema: type: string format: binary responses: '200': description: Chunk uploaded successfully content: application/json: schema: $ref: '#/components/schemas/ChunkUploadResult' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '409': description: Chunk already uploaded or out of sequence content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' get: operationId: getUploadSessionStatus summary: Get upload session status description: Retrieve the current status of a chunked upload session tags: - chunks security: - bearerAuth: [] - oauth2: [excititor:read] parameters: - name: session_id in: path required: true schema: type: string format: uuid responses: '200': description: Upload session status content: application/json: schema: $ref: '#/components/schemas/ChunkedUploadSession' '404': $ref: '#/components/responses/NotFound' delete: operationId: cancelUploadSession summary: Cancel upload session description: Cancel an active upload session and clean up partial data tags: - chunks security: - bearerAuth: [] - oauth2: [excititor:write] parameters: - name: session_id in: path required: true schema: type: string format: uuid responses: '204': description: Session cancelled '404': $ref: '#/components/responses/NotFound' /chunks/{session_id}/complete: post: operationId: completeChunkedUpload summary: Complete chunked upload description: Finalize a chunked upload and trigger VEX processing tags: - chunks security: - bearerAuth: [] - oauth2: [excititor:write] parameters: - name: session_id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChunkedUploadCompleteRequest' responses: '200': description: Upload completed, processing started content: application/json: schema: $ref: '#/components/schemas/VexIngestionJob' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '409': description: Missing chunks or invalid digest content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' /vex/ingest: post: operationId: ingestVexDocument summary: Ingest a VEX document description: Ingest a small VEX document directly (for documents < 10MB) tags: - vex security: - bearerAuth: [] - oauth2: [excititor:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VexIngestionRequest' application/vnd.openvex+json: schema: type: object application/vnd.csaf+json: schema: type: object application/vnd.cyclonedx+json: schema: type: object responses: '202': description: VEX document accepted for processing content: application/json: schema: $ref: '#/components/schemas/VexIngestionJob' '400': $ref: '#/components/responses/BadRequest' '413': description: Payload too large - use chunked upload content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' /vex/jobs/{job_id}: get: operationId: getIngestionJobStatus summary: Get ingestion job status description: Retrieve the status of a VEX ingestion job tags: - processing security: - bearerAuth: [] - oauth2: [excititor:read] parameters: - name: job_id in: path required: true schema: type: string format: uuid responses: '200': description: Job status content: application/json: schema: $ref: '#/components/schemas/VexIngestionJob' '404': $ref: '#/components/responses/NotFound' /vex/jobs: get: operationId: listIngestionJobs summary: List ingestion jobs description: List VEX ingestion jobs with filtering and pagination tags: - processing security: - bearerAuth: [] - oauth2: [excititor:read] parameters: - name: status in: query schema: type: string enum: [pending, processing, completed, failed] - name: created_after in: query schema: type: string format: date-time - name: created_before in: query schema: type: string format: date-time - name: page in: query schema: type: integer minimum: 1 default: 1 - name: page_size in: query schema: type: integer minimum: 1 maximum: 100 default: 20 responses: '200': description: List of jobs content: application/json: schema: $ref: '#/components/schemas/VexIngestionJobList' /health: get: operationId: healthCheck summary: Health check description: Service health check endpoint tags: - health security: [] responses: '200': description: Service healthy content: application/json: schema: $ref: '#/components/schemas/HealthStatus' '503': description: Service unhealthy content: application/json: schema: $ref: '#/components/schemas/HealthStatus' /health/ready: get: operationId: readinessCheck summary: Readiness check description: Kubernetes readiness probe endpoint tags: - health security: [] responses: '200': description: Service ready '503': description: Service not ready components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT oauth2: type: oauth2 flows: clientCredentials: tokenUrl: /oauth/token scopes: excititor:read: Read VEX data excititor:write: Write VEX data schemas: ChunkedUploadInitRequest: type: object required: - filename - total_size - content_type properties: filename: type: string description: Original filename total_size: type: integer minimum: 1 description: Total file size in bytes content_type: type: string enum: - application/json - application/vnd.openvex+json - application/vnd.csaf+json - application/vnd.cyclonedx+json expected_digest: type: string pattern: '^sha256:[a-f0-9]{64}$' description: Expected SHA-256 digest of complete file chunk_size: type: integer minimum: 1048576 maximum: 104857600 default: 10485760 description: Chunk size in bytes (1MB - 100MB, default 10MB) metadata: type: object additionalProperties: true description: Optional metadata for the upload ChunkedUploadSession: type: object required: - session_id - status - created_at properties: session_id: type: string format: uuid status: type: string enum: [active, completed, cancelled, expired] filename: type: string total_size: type: integer chunk_size: type: integer total_chunks: type: integer uploaded_chunks: type: array items: type: integer chunks_remaining: type: integer bytes_uploaded: type: integer created_at: type: string format: date-time expires_at: type: string format: date-time upload_url: type: string format: uri description: URL for chunk uploads ChunkUploadResult: type: object required: - chunk_index - received properties: chunk_index: type: integer received: type: boolean digest_verified: type: boolean bytes_received: type: integer chunks_remaining: type: integer ChunkedUploadCompleteRequest: type: object required: - final_digest properties: final_digest: type: string pattern: '^sha256:[a-f0-9]{64}$' description: SHA-256 digest of reassembled file process_immediately: type: boolean default: true description: Start processing immediately after assembly VexIngestionRequest: type: object required: - document properties: document: type: object description: VEX document (OpenVEX, CSAF, or CycloneDX format) format: type: string enum: [openvex, csaf, cyclonedx, auto] default: auto source: type: string description: Source identifier for the VEX document priority: type: string enum: [low, normal, high] default: normal metadata: type: object additionalProperties: true VexIngestionJob: type: object required: - job_id - status - created_at properties: job_id: type: string format: uuid status: type: string enum: [pending, validating, processing, indexing, completed, failed] format_detected: type: string enum: [openvex, csaf, cyclonedx, unknown] created_at: type: string format: date-time started_at: type: string format: date-time completed_at: type: string format: date-time document_digest: type: string pattern: '^sha256:[a-f0-9]{64}$' statements_count: type: integer description: Number of VEX statements processed products_count: type: integer description: Number of products affected vulnerabilities_count: type: integer description: Number of vulnerabilities referenced errors: type: array items: $ref: '#/components/schemas/ProcessingError' warnings: type: array items: type: string result_ref: type: string description: Reference to processing result VexIngestionJobList: type: object required: - jobs - total_count properties: jobs: type: array items: $ref: '#/components/schemas/VexIngestionJob' total_count: type: integer page: type: integer page_size: type: integer next_page_token: type: string ProcessingError: type: object required: - code - message properties: code: type: string message: type: string location: type: string description: JSON path to error location details: type: object additionalProperties: true HealthStatus: type: object required: - status properties: status: type: string enum: [healthy, degraded, unhealthy] version: type: string uptime_seconds: type: integer checks: type: array items: type: object properties: name: type: string status: type: string enum: [pass, warn, fail] message: type: string ProblemDetails: type: object required: - type - title - status properties: type: type: string format: uri title: type: string status: type: integer detail: type: string instance: type: string format: uri errors: type: array items: type: object properties: field: type: string message: type: string responses: BadRequest: description: Bad request content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' Unauthorized: description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' TooManyRequests: description: Rate limit exceeded content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' headers: Retry-After: schema: type: integer description: Seconds until rate limit resets