Stella Ops API Overview
The shared contract every Stella Ops HTTP API follows: authentication, tenancy, pagination, idempotency, rate limits, error envelopes, and determinism guarantees. Read this once, then rely on each service’s OpenAPI document for endpoint-specific details.
Audience: API client integrators, SDK authors, and service developers.
Last updated: 2026-05-30 (doc ↔ code reconciliation)
Scope
Shared conventions for all Stella Ops HTTP APIs. Per-service schemas live under src/Api/StellaOps.Api.OpenApi/<service>/openapi.yaml; the aggregate spec is composed by compose.mjs to src/Api/StellaOps.Api.OpenApi/stella.yaml. Note: that aggregate currently carries x-stellaops-contract-status: contains-draft-stubs (draft stub services include export-center, graph, jobengine, policy, scheduler), so it is not a fully authoritative shipped contract for those services.
Related Documentation
- OpenAPI Discovery - Discovery endpoints and aggregation
- Schema Validation - JSON Schema validation for microservice endpoints
- OpenAPI Aggregation - Gateway OpenAPI document generation
Authentication & tenancy
- Auth: Bearer tokens (
Authorization: Bearer <token>), validated as JWTs issued by Authority (OAuth 2.1). The resource server validates the tokenaudclaim against its configured audiences (StellaOpsResourceServerOptions.Audiences->jwt.TokenValidationParameters.ValidAudiences), so service accounts must request a token whoseaudincludes the target service. - Tenancy: Tenant identity is resolved claim-first, not header-first.
StellaOpsTenantResolverreads the tenant exclusively from envelope-bound JWT claims, in priority orderstellaops:tenant(canonical) ->tenant_id->tenant->tid(legacy). The header fallbacks (X-StellaOps-TenantId,X-StellaOps-Tenant,X-Stella-Tenant,X-Tenant-Id) are stripped at ingress and are no longer honoured as a tenancy source.- Endpoints that require a tenant use the
RequireTenant()endpoint filter. A request with no resolvable tenant is rejected with HTTP400and anapplication/problem+jsonbody ({ "type", "title", "status": 400, "detail", "error_code": "tenant_missing" }) — not403and noterror.code = TENANT_REQUIRED. Other failure codes aretenant_conflictandtenant_invalid_format. - Exception: a small number of legacy Authority tenant-scoped surfaces (Console / Console-Admin tiers, per AUTH-DRIFT-003) still require the
X-StellaOps-TenantIdheader and treat the bearer claim as informational only; on those surfaces a mismatched header vs. claim returns403and a missing header returns400 tenant_header_missing. These are documented per path.
- Endpoints that require a tenant use the
- Scopes: Each endpoint documents required scopes; clients must send least-privilege tokens. The canonical scope catalogue is
StellaOpsScopes(src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs); it is the JWT authorization surface (what may appear in thescopeclaim). Token issuance filters requested scopes againstauthority.clients.allowed_scopes— theauthority.permissionstable is the Console RBAC catalogue and is not consulted at request time. Scope-string formats are not uniform: most are colon-delimited (scanner:read,policy:write,vuln:view) but several families are dot-delimited (concelier.merge,packs.read,export.viewer,notify.viewer,ops.health,analytics.read).
Pagination
- Cursor-based. The canonical shared parameters are
limit(integer, default 50, max 200 unless noted) andcursor(opaque string); see_shared/parameters/paging.yaml. Some service specs use the camelCase aliasespageSize(max 200, default 50) /pageToken(e.g. Notifier) — confirm the exact names in each service’s OpenAPI document. - List responses carry page metadata (
_shared/schemas/common.yamlPageMetadata):hasMore(boolean), andnextCursor/previousCursorwhen more pages are available. - All response field names are camelCase (services serialize with
JsonNamingPolicy.CamelCase); there are no snake_case API fields. - Ordering is deterministic per endpoint (documented under each path).
Idempotency
- Some write endpoints accept an
Idempotency-Keyheader (UUID) to de-duplicate retries (e.g. the Notifier API declares itrequired,format: uuid); servers de-duplicate on key + tenant + path. This is per-endpoint, not a platform-wide guarantee — check each service’s spec. - Idempotent methods: GET/HEAD/OPTIONS always; POST/PUT/PATCH/DELETE idempotent only when an
Idempotency-Keyis provided and the endpoint documents support for it.
Rate limits
- The gateway rate limiter (
StellaOps.Router.GatewayRateLimitMiddleware) emitsX-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset(the reset instant as UTC epoch seconds) on responses where a concrete window+limit applies. Limits are partitioned per tenant + microservice. - On
429 Too Many Requeststhe limiter always sets aRetry-Afterheader (seconds, minimum 1) and returns the rate-limit error body shown under “Error envelope”; clients should back off usingRetry-After.
Error envelope
The canonical error envelope (_shared/schemas/common.yaml ErrorEnvelope, served as application/json) is flat — the fields are not nested under an error object, and field names are camelCase:
{
"code": "service_unavailable",
"message": "human readable",
"traceId": "<trace or correlation id>"
}
codeandmessageare required;traceIdis the optional correlation identifier and maps to server traces/logs (CLI/Console surface it in verbose output).- Minimal-API services emit the same flat shape but commonly name the machine-readable code field
errorinstead ofcode(e.g. Notify:{ "error": "tenant_missing", "message": "...", "traceId": "..." }). Theerrorvalue is the code string, not a nested object. - Tenant and some validation rejections instead use RFC 7807
application/problem+json({ "type", "title", "status", "detail", "error_code" }). Treaterror_code/error/codeas the machine-readable discriminator depending on the surface. - The 429 body adds rate-limit context:
{ "error": "rate_limit_exceeded", "message", "retryAfter", "limit", "current", "window", "scope" }.
Headers
- Observability: the platform configures OpenTelemetry tracing (
StellaOps.Telemetry.Core), which propagates W3C Trace Context viatraceparent(and optionallybaggage). Clients should forwardtraceparentwhen chaining calls; the correlation identifier is surfaced back astraceIdin the error envelope. - Content negotiation:
Acceptdefaults toapplication/json; NDJSON endpoints useapplication/x-ndjson. - All times are UTC ISO-8601; hashes are lowercase hex.
Determinism & offline
- APIs must return stable ordering for identical inputs; list endpoints document their sort keys.
- Clients should operate offline using cached responses where supported; air-gap endpoints document required bundles/trust roots.
Checklist for new/changed endpoints
- [ ] Added request/response examples (see
examples/folders). - [ ] Spectral lint passes (
npm run api:lint; runs@stoplight/spectral-cliagainstsrc/Api/StellaOps.Api.OpenApi/**/*.yaml). - [ ] Compat report reviewed (
npm run api:compatvssrc/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml). - [ ] Aggregate spec recomposed (
npm run api:compose->src/Api/StellaOps.Api.OpenApi/stella.yaml) and changelog regenerated (npm run api:changelog). - [ ] Error envelope and headers documented.
- [ ] Pagination, idempotency, tenancy, scopes documented.
