openapi: 3.0.3 info: title: StellaOps Graph Gateway (draft) version: 0.0.2-pre description: | Draft API surface for graph search/query/paths/diff/export with streaming tiles, cost budgets, overlays, and RBAC headers. Aligns with sprint 0207 Wave 1 outline (GRAPH-API-28-001..011). servers: - url: https://gateway.local/api security: - bearerAuth: [] paths: /graph/search: post: summary: Search graph nodes with prefix/exact semantics and filters security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SearchRequest' responses: '200': description: Stream of search tiles (NDJSON) content: application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': { $ref: '#/components/responses/ValidationError' } '401': { $ref: '#/components/responses/Unauthorized' } '429': { $ref: '#/components/responses/BudgetExceeded' } /graph/query: post: summary: Execute graph query with budgeted streaming tiles security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/QueryRequest' responses: '200': description: Stream of query tiles (NDJSON) content: application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': { $ref: '#/components/responses/ValidationError' } '401': { $ref: '#/components/responses/Unauthorized' } '429': { $ref: '#/components/responses/BudgetExceeded' } /graph/paths: post: summary: Find constrained paths between node sets (depth ≤ 6) security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PathsRequest' responses: '200': description: Stream of path tiles ordered by hop content: application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': { $ref: '#/components/responses/ValidationError' } '401': { $ref: '#/components/responses/Unauthorized' } '429': { $ref: '#/components/responses/BudgetExceeded' } /graph/diff: post: summary: Stream diff between two graph snapshots with overlay deltas security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DiffRequest' responses: '200': description: Stream of diff tiles (added/removed/changed) content: application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': { $ref: '#/components/responses/ValidationError' } '401': { $ref: '#/components/responses/Unauthorized' } /graph/export: post: summary: Request export job for snapshot or query result security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ExportRequest' responses: '202': description: Export job accepted content: application/json: schema: $ref: '#/components/schemas/ExportJob' '400': { $ref: '#/components/responses/ValidationError' } '401': { $ref: '#/components/responses/Unauthorized' } /graph/export/{jobId}: get: summary: Check export job status or download manifest security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/RequestIdHeader' - name: jobId in: path required: true schema: type: string responses: '200': description: Job status content: application/json: schema: $ref: '#/components/schemas/ExportJob' '404': description: Job not found content: application/json: schema: $ref: '#/components/schemas/Error' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT parameters: TenantHeader: name: X-Stella-Tenant in: header required: true schema: type: string description: Tenant identifier enforced on all routes. RequestIdHeader: name: X-Request-Id in: header required: false schema: type: string description: Optional caller-provided correlation id, echoed in responses. schemas: CostBudget: type: object properties: limit: type: integer minimum: 1 remaining: type: integer minimum: 0 consumed: type: integer minimum: 0 required: [limit, remaining, consumed] TileEnvelope: type: object properties: type: type: string enum: [node, edge, stats, cursor, diagnostic] seq: type: integer minimum: 0 cost: $ref: '#/components/schemas/CostBudget' data: type: object description: Payload varies by tile type (node/edge record, stats snapshot, cursor token, or diagnostic info). required: [type, seq] SearchRequest: type: object properties: query: type: string description: Prefix or exact text; required unless filters present. kinds: type: array items: type: string limit: type: integer default: 50 maximum: 500 filters: type: object additionalProperties: true ordering: type: string enum: [relevance, id] required: [kinds] QueryRequest: type: object properties: dsl: type: string description: DSL expression for graph traversal (mutually exclusive with filter). filter: type: object description: Structured filter alternative to DSL. budget: type: object properties: nodeCap: { type: integer } edgeCap: { type: integer } timeMs: { type: integer } overlays: type: array items: type: string enum: [policy, vex, advisory] explain: type: string enum: [none, minimal, full] default: none anyOf: - required: [dsl] - required: [filter] PathsRequest: type: object properties: sourceIds: type: array items: { type: string } minItems: 1 targetIds: type: array items: { type: string } minItems: 1 maxDepth: type: integer maximum: 6 default: 4 constraints: type: object properties: edgeKinds: type: array items: { type: string } fanoutCap: type: integer overlays: type: array items: { type: string } required: [sourceIds, targetIds] DiffRequest: type: object properties: snapshotA: { type: string } snapshotB: { type: string } filters: type: object additionalProperties: true required: [snapshotA, snapshotB] ExportRequest: type: object properties: snapshotId: type: string queryRef: type: string formats: type: array items: type: string enum: [graphml, csv, ndjson, png, svg] includeOverlays: type: boolean default: false anyOf: - required: [snapshotId] - required: [queryRef] required: [formats] ExportJob: type: object properties: jobId: { type: string } status: { type: string, enum: [pending, running, succeeded, failed] } checksumManifestUrl: { type: string, format: uri } downloadUrl: { type: string, format: uri } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } message: { type: string } required: [jobId, status] Error: type: object properties: error: type: string enum: [GRAPH_BUDGET_EXCEEDED, GRAPH_VALIDATION_FAILED, GRAPH_RATE_LIMITED, GRAPH_UNAUTHORIZED] message: type: string details: type: object request_id: type: string required: [error, message] responses: ValidationError: description: Request failed validation content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Missing or invalid credentials content: application/json: schema: $ref: '#/components/schemas/Error' BudgetExceeded: description: Budget exhausted mid-stream; includes partial cursor details content: application/json: schema: $ref: '#/components/schemas/Error'