openapi: 3.1.0 info: title: StellaOps Graph Platform API version: 1.0.0 description: | Comprehensive API for the StellaOps Graph Platform providing dependency visualization, reachability analysis, path finding, and UI integration capabilities. Unblocks Web/UI chains (11+ tasks). This API enables: - Graph queries with tile-based streaming responses - Full-text and faceted search across graph entities - Path finding between nodes with reachability evidence - Graph diff/comparison between snapshots - Export in multiple formats (NDJSON, CSV, GraphML, PNG, SVG) - Overlay support for UI visualization - RichGraph v1 integration for reachability claims - Rate limiting and audit logging ## Blocker References - SPRINT_0209_ui_i (11 tasks) - Graph platform contracts - GRAPH-28-007 through GRAPH-28-010 - Signals integration - CONTRACT-RICHGRAPH-V1-015 - Reachability graph schema 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: https://graph.stella-ops.org/v1 description: Production Graph API - url: https://graph.staging.stella-ops.org/v1 description: Staging Graph API tags: - name: query description: Graph query operations - name: search description: Full-text and faceted search - name: path description: Path finding between nodes - name: diff description: Graph comparison operations - name: export description: Graph export in various formats - name: reachability description: RichGraph reachability operations - name: overlay description: UI overlay data - name: meta description: Service health and metadata paths: /healthz: get: operationId: getHealth summary: Service health check tags: - meta responses: '200': description: Service healthy content: application/json: schema: $ref: '#/components/schemas/HealthResponse' '503': description: Service unavailable content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /graphs: get: operationId: listGraphs summary: List available graphs tags: - meta parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' - name: status in: query schema: $ref: '#/components/schemas/GraphBuildStatus' responses: '200': description: List of graphs content: application/json: schema: $ref: '#/components/schemas/GraphListResponse' /graphs/{graph_id}: get: operationId: getGraph summary: Get graph metadata tags: - meta parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' responses: '200': description: Graph metadata content: application/json: schema: $ref: '#/components/schemas/GraphMetadata' '404': $ref: '#/components/responses/NotFound' /graphs/{graph_id}/status: get: operationId: getGraphStatus summary: Get graph build status tags: - meta parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' responses: '200': description: Graph build status content: application/json: schema: $ref: '#/components/schemas/GraphStatus' '404': $ref: '#/components/responses/NotFound' /graphs/{graph_id}/query: post: operationId: queryGraph summary: Query graph nodes and edges description: | Executes a graph query and returns results as a tile stream. Supports budget limits to control response size and resource usage. Response format is a stream of TileEnvelope objects (NDJSON for streaming). tags: - query parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GraphQueryRequest' responses: '200': description: Query results as tile stream content: application/json: schema: $ref: '#/components/schemas/GraphQueryResponse' application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': $ref: '#/components/responses/BadRequest' '429': $ref: '#/components/responses/RateLimited' /graphs/{graph_id}/search: post: operationId: searchGraph summary: Full-text search across graph description: | Performs full-text search with optional faceted filtering. Results are ranked by relevance. tags: - search parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GraphSearchRequest' responses: '200': description: Search results content: application/json: schema: $ref: '#/components/schemas/GraphSearchResponse' application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': $ref: '#/components/responses/BadRequest' /graphs/{graph_id}/nodes: get: operationId: listNodes summary: List graph nodes tags: - query parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' - name: kinds in: query style: form explode: false schema: type: array items: type: string - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/Cursor' responses: '200': description: Node list content: application/json: schema: $ref: '#/components/schemas/NodeListResponse' /graphs/{graph_id}/nodes/{node_id}: get: operationId: getNode summary: Get node details tags: - query parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' - name: node_id in: path required: true schema: type: string - name: include_edges in: query schema: type: boolean default: false - name: include_overlays in: query schema: type: boolean default: false responses: '200': description: Node details content: application/json: schema: $ref: '#/components/schemas/NodeDetail' '404': $ref: '#/components/responses/NotFound' /graphs/{graph_id}/edges: get: operationId: listEdges summary: List graph edges tags: - query parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' - name: kinds in: query style: form explode: false schema: type: array items: type: string - name: source in: query schema: type: string - name: target in: query schema: type: string - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/Cursor' responses: '200': description: Edge list content: application/json: schema: $ref: '#/components/schemas/EdgeListResponse' /graphs/{graph_id}/path: post: operationId: findPath summary: Find paths between nodes description: | Finds paths from source nodes to target nodes with optional constraints. Results include reachability evidence when available. tags: - path parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GraphPathRequest' responses: '200': description: Path results content: application/json: schema: $ref: '#/components/schemas/GraphPathResponse' application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': $ref: '#/components/responses/BadRequest' /graphs/{graph_id}/diff: post: operationId: diffGraphs summary: Compare graph snapshots description: | Computes the difference between two graph snapshots. Returns added, removed, and modified nodes/edges. tags: - diff parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GraphDiffRequest' responses: '200': description: Diff results content: application/json: schema: $ref: '#/components/schemas/GraphDiffResponse' application/x-ndjson: schema: $ref: '#/components/schemas/TileEnvelope' '400': $ref: '#/components/responses/BadRequest' /graphs/{graph_id}/export: post: operationId: exportGraph summary: Export graph in various formats description: | Exports graph data in the requested format. Supports NDJSON, CSV, GraphML, PNG, and SVG. tags: - export parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GraphExportRequest' responses: '200': description: Export data content: application/x-ndjson: schema: type: string text/csv: schema: type: string application/xml: schema: type: string image/png: schema: type: string format: binary image/svg+xml: schema: type: string '400': $ref: '#/components/responses/BadRequest' /graphs/{graph_id}/overlays: get: operationId: listOverlays summary: List available overlays tags: - overlay parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' responses: '200': description: Available overlays content: application/json: schema: $ref: '#/components/schemas/OverlayListResponse' post: operationId: getOverlayData summary: Get overlay data for nodes description: | Retrieves overlay data (e.g., risk scores, reachability status, policy violations) for the specified nodes. tags: - overlay parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/GraphId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OverlayRequest' responses: '200': description: Overlay data content: application/json: schema: $ref: '#/components/schemas/OverlayResponse' /reachability/graphs: get: operationId: listReachabilityGraphs summary: List RichGraph reachability graphs tags: - reachability parameters: - $ref: '#/components/parameters/TenantId' - name: artifact_id in: query schema: type: string - name: since in: query schema: type: string format: date-time - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: RichGraph list content: application/json: schema: $ref: '#/components/schemas/RichGraphListResponse' /reachability/graphs/{graph_hash}: get: operationId: getRichGraph summary: Get RichGraph by hash description: | Retrieves a RichGraph reachability document by its BLAKE3 hash. Returns the full richgraph-v1 document. tags: - reachability parameters: - $ref: '#/components/parameters/TenantId' - name: graph_hash in: path required: true description: BLAKE3 hash of the graph (format blake3:hex) schema: type: string pattern: '^blake3:[a-f0-9]{64}$' responses: '200': description: RichGraph document content: application/json: schema: $ref: '#/components/schemas/RichGraphV1' '404': $ref: '#/components/responses/NotFound' /reachability/graphs/{graph_hash}/dsse: get: operationId: getRichGraphDsse summary: Get RichGraph DSSE envelope tags: - reachability parameters: - $ref: '#/components/parameters/TenantId' - name: graph_hash in: path required: true schema: type: string pattern: '^blake3:[a-f0-9]{64}$' responses: '200': description: DSSE envelope content: application/json: schema: $ref: '#/components/schemas/DsseEnvelope' '404': $ref: '#/components/responses/NotFound' /reachability/query: post: operationId: queryReachability summary: Query reachability between symbols description: | Queries whether target symbols are reachable from entry points. Returns reachability evidence and confidence levels. tags: - reachability parameters: - $ref: '#/components/parameters/TenantId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReachabilityQueryRequest' responses: '200': description: Reachability results content: application/json: schema: $ref: '#/components/schemas/ReachabilityQueryResponse' /reachability/symbols/{symbol_id}: get: operationId: getSymbol summary: Get symbol details description: | Retrieves details for a specific symbol including its reachability status and evidence sources. tags: - reachability parameters: - $ref: '#/components/parameters/TenantId' - name: symbol_id in: path required: true description: Symbol ID (format sym:lang:base64url) schema: type: string responses: '200': description: Symbol details content: application/json: schema: $ref: '#/components/schemas/SymbolDetail' '404': $ref: '#/components/responses/NotFound' components: parameters: TenantId: name: X-Tenant-Id in: header required: true description: Tenant identifier for multi-tenant isolation schema: type: string minLength: 1 maxLength: 64 GraphId: name: graph_id in: path required: true description: Graph unique identifier schema: type: string PageSize: name: page_size in: query description: Number of items per page (default 100, max 500) schema: type: integer minimum: 1 maximum: 500 default: 100 PageToken: name: page_token in: query description: Pagination token from previous response schema: type: string Cursor: name: cursor in: query description: Cursor for resuming pagination schema: type: string schemas: HealthResponse: type: object required: - status - service properties: status: type: string enum: - ok - degraded - unhealthy service: type: string const: graph version: type: string indexer_lag_ms: type: integer format: int64 GraphBuildStatus: type: string enum: - building - ready - failed - stale GraphMetadata: type: object required: - graph_id - tenant_id - status - created_at properties: graph_id: type: string tenant_id: type: string status: $ref: '#/components/schemas/GraphBuildStatus' created_at: type: string format: date-time updated_at: type: string format: date-time built_at: type: string format: date-time node_count: type: integer format: int64 edge_count: type: integer format: int64 artifact_digest: type: string source_sbom_id: type: string richgraph_hash: type: string pattern: '^blake3:[a-f0-9]{64}$' GraphStatus: type: object required: - graph_id - status properties: graph_id: type: string status: $ref: '#/components/schemas/GraphBuildStatus' built_at: type: string format: date-time tenant: type: string build_progress: type: number minimum: 0 maximum: 1 build_error: type: string GraphListResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/GraphMetadata' next_page_token: type: string total_count: type: integer format: int64 GraphQueryRequest: type: object required: - kinds properties: kinds: type: array items: type: string minItems: 1 description: Node kinds to query (e.g., package, method, class) query: type: string description: Query expression filters: type: object additionalProperties: true description: Filter conditions limit: type: integer minimum: 1 maximum: 500 default: 100 cursor: type: string include_edges: type: boolean default: true include_stats: type: boolean default: true include_overlays: type: boolean default: false budget: $ref: '#/components/schemas/QueryBudget' GraphQueryResponse: type: object required: - tiles properties: tiles: type: array items: $ref: '#/components/schemas/TileEnvelope' stats: $ref: '#/components/schemas/StatsTile' cursor: $ref: '#/components/schemas/CursorTile' GraphSearchRequest: type: object required: - kinds properties: kinds: type: array items: type: string minItems: 1 query: type: string description: Full-text search query filters: type: object additionalProperties: true limit: type: integer minimum: 1 maximum: 500 default: 100 ordering: type: string enum: - relevance - id default: relevance cursor: type: string GraphSearchResponse: type: object required: - results properties: results: type: array items: $ref: '#/components/schemas/SearchResult' total_hits: type: integer format: int64 facets: type: object additionalProperties: type: array items: $ref: '#/components/schemas/FacetValue' cursor: type: string SearchResult: type: object required: - id - kind - score properties: id: type: string kind: type: string label: type: string score: type: number highlights: type: object additionalProperties: type: array items: type: string FacetValue: type: object required: - value - count properties: value: type: string count: type: integer GraphPathRequest: type: object required: - sources - targets properties: sources: type: array items: type: string minItems: 1 description: Source node IDs targets: type: array items: type: string minItems: 1 description: Target node IDs kinds: type: array items: type: string description: Edge kinds to traverse max_depth: type: integer minimum: 1 maximum: 6 default: 4 filters: type: object additionalProperties: true include_overlays: type: boolean default: false budget: $ref: '#/components/schemas/QueryBudget' GraphPathResponse: type: object required: - paths properties: paths: type: array items: $ref: '#/components/schemas/PathResult' stats: $ref: '#/components/schemas/PathStats' PathResult: type: object required: - nodes - edges properties: nodes: type: array items: $ref: '#/components/schemas/NodeTile' edges: type: array items: $ref: '#/components/schemas/EdgeTile' total_hops: type: integer confidence: type: number minimum: 0 maximum: 1 evidence: type: array items: type: string PathStats: type: object properties: paths_found: type: integer nodes_visited: type: integer edges_traversed: type: integer max_depth_reached: type: integer GraphDiffRequest: type: object required: - snapshot_a - snapshot_b properties: snapshot_a: type: string description: First snapshot ID or timestamp snapshot_b: type: string description: Second snapshot ID or timestamp include_edges: type: boolean default: true include_stats: type: boolean default: true budget: $ref: '#/components/schemas/QueryBudget' GraphDiffResponse: type: object required: - summary properties: summary: $ref: '#/components/schemas/DiffSummary' changes: type: array items: $ref: '#/components/schemas/DiffTile' DiffSummary: type: object properties: nodes_added: type: integer nodes_removed: type: integer nodes_changed: type: integer edges_added: type: integer edges_removed: type: integer edges_changed: type: integer DiffTile: type: object required: - entity_type - change_type - id properties: entity_type: type: string enum: - node - edge change_type: type: string enum: - added - removed - changed id: type: string before: type: object additionalProperties: true after: type: object additionalProperties: true GraphExportRequest: type: object properties: format: type: string enum: - ndjson - csv - graphml - png - svg default: ndjson include_edges: type: boolean default: true snapshot_id: type: string kinds: type: array items: type: string query: type: string filters: type: object additionalProperties: true QueryBudget: type: object description: Resource limits for query execution properties: tiles: type: integer minimum: 1 maximum: 6000 default: 6000 description: Maximum number of tiles to return nodes: type: integer minimum: 1 default: 5000 description: Maximum number of nodes edges: type: integer minimum: 1 default: 10000 description: Maximum number of edges CostBudget: type: object required: - limit - remaining - consumed properties: limit: type: integer remaining: type: integer consumed: type: integer TileEnvelope: type: object required: - type - seq - data properties: type: type: string enum: - node - edge - stats - cursor - diff - error seq: type: integer description: Sequence number within stream data: oneOf: - $ref: '#/components/schemas/NodeTile' - $ref: '#/components/schemas/EdgeTile' - $ref: '#/components/schemas/StatsTile' - $ref: '#/components/schemas/CursorTile' - $ref: '#/components/schemas/DiffTile' cost: $ref: '#/components/schemas/CostBudget' NodeTile: type: object required: - id - kind - tenant properties: id: type: string kind: type: string tenant: type: string label: type: string attributes: type: object additionalProperties: true path_hop: type: integer description: Hop distance from source in path queries overlays: type: object additionalProperties: $ref: '#/components/schemas/OverlayPayload' EdgeTile: type: object required: - id - kind - source - target properties: id: type: string kind: type: string default: depends_on tenant: type: string source: type: string target: type: string attributes: type: object additionalProperties: true StatsTile: type: object properties: nodes: type: integer edges: type: integer CursorTile: type: object required: - token properties: token: type: string resume_url: type: string format: uri OverlayPayload: type: object required: - kind - version - data properties: kind: type: string version: type: string data: type: object additionalProperties: true OverlayListResponse: type: object required: - overlays properties: overlays: type: array items: $ref: '#/components/schemas/OverlayInfo' OverlayInfo: type: object required: - kind - version - name properties: kind: type: string version: type: string name: type: string description: type: string OverlayRequest: type: object required: - node_ids - overlay_kinds properties: node_ids: type: array items: type: string minItems: 1 maxItems: 100 overlay_kinds: type: array items: type: string minItems: 1 OverlayResponse: type: object required: - overlays properties: overlays: type: object additionalProperties: type: object additionalProperties: $ref: '#/components/schemas/OverlayPayload' NodeListResponse: type: object required: - nodes properties: nodes: type: array items: $ref: '#/components/schemas/NodeTile' metadata: $ref: '#/components/schemas/PageMetadata' EdgeListResponse: type: object required: - edges properties: edges: type: array items: $ref: '#/components/schemas/EdgeTile' metadata: $ref: '#/components/schemas/PageMetadata' NodeDetail: type: object required: - node properties: node: $ref: '#/components/schemas/NodeTile' incoming_edges: type: array items: $ref: '#/components/schemas/EdgeTile' outgoing_edges: type: array items: $ref: '#/components/schemas/EdgeTile' overlays: type: object additionalProperties: $ref: '#/components/schemas/OverlayPayload' PageMetadata: type: object properties: has_more: type: boolean next_cursor: type: string total_count: type: integer format: int64 # RichGraph V1 schemas (from richgraph-v1 contract) RichGraphListResponse: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/RichGraphSummary' next_page_token: type: string RichGraphSummary: type: object required: - graph_hash - artifact_id - created_at properties: graph_hash: type: string pattern: '^blake3:[a-f0-9]{64}$' artifact_id: type: string artifact_digest: type: string created_at: type: string format: date-time node_count: type: integer edge_count: type: integer root_count: type: integer RichGraphV1: type: object required: - schema - nodes - edges - roots properties: schema: type: string const: richgraph-v1 analyzer: $ref: '#/components/schemas/AnalyzerInfo' nodes: type: array items: $ref: '#/components/schemas/RichGraphNode' edges: type: array items: $ref: '#/components/schemas/RichGraphEdge' roots: type: array items: $ref: '#/components/schemas/RichGraphRoot' AnalyzerInfo: type: object required: - name - version properties: name: type: string default: scanner.reachability version: type: string default: '0.1.0' toolchain_digest: type: string RichGraphNode: type: object required: - id - symbol_id - lang - kind properties: id: type: string symbol_id: type: string pattern: '^sym:[a-z]+:[A-Za-z0-9_-]+$' lang: type: string enum: - java - dotnet - go - node - rust - python - ruby - php - binary - shell kind: type: string enum: - method - function - class - module - trait - struct display: type: string code_id: type: string pattern: '^code:[a-z]+:[A-Za-z0-9_-]+$' purl: type: string build_id: type: string symbol_digest: type: string pattern: '^sha256:[a-f0-9]{64}$' evidence: type: array items: type: string enum: - import - reloc - disasm - runtime attributes: type: object additionalProperties: true RichGraphEdge: type: object required: - from - to - kind - confidence properties: from: type: string to: type: string kind: type: string enum: - call - virtual - indirect - data - init purl: type: string symbol_digest: type: string pattern: '^sha256:[a-f0-9]{64}$' confidence: type: number minimum: 0 maximum: 1 evidence: type: array items: type: string candidates: type: array items: type: string RichGraphRoot: type: object required: - id - phase properties: id: type: string phase: type: string enum: - runtime - load - init - test source: type: string DsseEnvelope: type: object required: - payloadType - payload - signatures properties: payloadType: type: string payload: type: string format: byte signatures: type: array items: $ref: '#/components/schemas/DsseSignature' DsseSignature: type: object required: - keyid - sig properties: keyid: type: string sig: type: string format: byte ReachabilityQueryRequest: type: object required: - artifact_id - targets properties: artifact_id: type: string targets: type: array items: type: string minItems: 1 description: Target symbol IDs or PURLs to check reachability from_entry_points: type: boolean default: true include_paths: type: boolean default: false max_depth: type: integer minimum: 1 maximum: 10 default: 6 ReachabilityQueryResponse: type: object required: - artifact_id - results properties: artifact_id: type: string graph_hash: type: string results: type: array items: $ref: '#/components/schemas/ReachabilityResult' ReachabilityResult: type: object required: - target - reachable properties: target: type: string reachable: type: boolean confidence: type: number minimum: 0 maximum: 1 evidence: type: array items: type: string path: type: array items: type: string description: Symbol IDs in path from entry point to target depth: type: integer SymbolDetail: type: object required: - symbol_id - lang - kind properties: symbol_id: type: string lang: type: string kind: type: string display: type: string purl: type: string reachability_status: type: string enum: - reachable - unreachable - unknown evidence: type: array items: type: string incoming_calls: type: integer outgoing_calls: type: integer graphs: type: array items: type: string description: Graph hashes where this symbol appears ErrorResponse: type: object required: - error - message properties: error: type: string message: type: string details: type: object additionalProperties: true request_id: type: string responses: BadRequest: description: Invalid request parameters content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' RateLimited: description: Rate limit exceeded headers: X-RateLimit-Limit: schema: type: integer X-RateLimit-Remaining: schema: type: integer X-RateLimit-Reset: schema: type: integer content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT apiKey: type: apiKey in: header name: X-API-Key security: - bearerAuth: [] - apiKey: []