11 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	Export Center REST API
Audience: Platform integrators, Console/CLI developers, and automation engineers orchestrating export runs.
Base route:/api/export/*behind the StellaOps gateway; requires Authority-issued tokens with export scopes.
This reference describes the Export Center API introduced in Export Center Phase 1 (Epic 10) and extended in Phase 2. Use it alongside the Export Center Architecture and Profiles guides for service-level semantics.
Status: Endpoint implementation lands with
EXPORT-SVC-35-006(Sprint 35) and related follow-on tasks. As of the current build the WebService hosts only the template stub; use this contract for coordination and update once the API is wired.
1. Authentication and headers
- Authorization: Bearer tokens in Authorization: Bearer <token>paired with DPoP proof. Required scopes per endpoint:- export:profile:managefor profile CRUD.
- export:runto submit and cancel runs.
- export:readto list and inspect runs.
- export:downloadfor bundle downloads and manifests.
 
- Tenant context: Provide X-Stella-Tenantwhen the token carries multiple tenants; defaults to token tenant otherwise.
- Idempotency: Mutating endpoints accept Idempotency-Key(UUID). Retrying with the same key returns the original result.
- Rate limits and quotas: Responses include X-Stella-Quota-Limit,X-Stella-Quota-Remaining, andX-Stella-Quota-Reset. Exceeding quotas returns429 Too Many RequestswithERR_EXPORT_QUOTA.
- Content negotiation: Requests and responses use application/json; charset=utf-8unless otherwise stated. Downloads stream binary content with profile-specific media types.
- SSE: Event streams set Content-Type: text/event-streamand keep connections alive with comment heartbeats every 15 seconds.
2. Error model
Errors follow standard HTTP codes with structured payloads:
{
  "code": "ERR_EXPORT_002",
  "message": "Profile not found for tenant acme",
  "details": [],
  "traceId": "01J9N4Y4K2XY8C5V7T2S",
  "timestamp": "2025-10-29T13:42:11Z"
}
| Code | Description | Typical HTTP status | Notes | 
|---|---|---|---|
| ERR_EXPORT_001 | Validation failure (selectors, configuration) | 400 | detailsenumerates offending fields. | 
| ERR_EXPORT_002 | Profile missing or not accessible for tenant | 404 | Returned on run submission or profile fetch. | 
| ERR_EXPORT_003 | Concurrency or quota exceeded | 429 | Includes retryAfterSecondsindetails. | 
| ERR_EXPORT_004 | Adapter failure (schema mismatch, upstream outage) | 502 | Worker logs contain adapter error reason. | 
| ERR_EXPORT_005 | Signing or KMS error | 500 | Run marked failed with errorCode=signing. | 
| ERR_EXPORT_006 | Distribution failure (HTTP, OCI, object storage) | 502 | detailslists failing distribution driver. | 
| ERR_EXPORT_007 | Run canceled or expired | 409 | Includes cancel author and timestamp. | 
| ERR_EXPORT_BASE_MISSING | Base manifest for delta exports not found | 400 | Specific to mirror:delta. | 
| ERR_EXPORT_EMPTY | No records matched selectors (when allowEmpty=false) | 422 | Useful for guard-railled automation. | 
| ERR_EXPORT_QUOTA | Daily quota exhausted | 429 | Always paired with quota headers. | 
All responses include traceId for correlation with logs and metrics.
3. Profiles endpoints
3.1 List profiles
GET /api/export/profiles?kind=json&variant=raw&page=1&pageSize=20
Scopes: export:read
Returns tenant-scoped profiles. Response headers: X-Total-Count, Link for pagination.
Response
{
  "items": [
    {
      "profileId": "prof-json-raw",
      "name": "Daily JSON Raw",
      "kind": "json",
      "variant": "raw",
      "distribution": ["http", "object"],
      "retention": {"mode": "days", "value": 14},
      "createdAt": "2025-10-23T08:00:00Z",
      "createdBy": "user:ops"
    }
  ],
  "page": 1,
  "pageSize": 20
}
3.2 Get a profile
GET /api/export/profiles/{profileId}
Scopes: export:read
Returns full configuration, including config payload, distribution options, and metadata.
3.3 Create a profile
POST /api/export/profiles
Scopes: export:profile:manage
Request
{
  "profileId": "prof-airgap-mirror",
  "name": "Airgap Mirror Weekly",
  "kind": "mirror",
  "variant": "full",
  "include": ["advisories", "vex", "sboms", "policy"],
  "distribution": ["http", "object"],
  "encryption": {
    "enabled": true,
    "recipientKeys": ["age1tenantkey..."],
    "strict": false
  },
  "retention": {"mode": "days", "value": 30}
}
Response 201
{
  "profileId": "prof-airgap-mirror",
  "version": 1,
  "createdAt": "2025-10-29T12:05:22Z",
  "createdBy": "user:ops",
  "status": "active"
}
3.4 Update profile metadata
PATCH /api/export/profiles/{profileId}
Scopes: export:profile:manage
Allows renaming, toggling distribution switches, or updating retention. Structural configuration updates (kind/variant/include) create a new revision; the API returns revisionCreated=true and the new profileId (e.g., prof-airgap-mirror@2).
3.5 Archive profile
POST /api/export/profiles/{profileId}:archive
Scopes: export:profile:manage
Marks profile as inactive; existing runs remain accessible. Use :restore to reactivate.
4. Run management
4.1 Submit an export run
POST /api/export/runs
Scopes: export:run
Request
{
  "profileId": "prof-json-raw",
  "selectors": {
    "tenants": ["acme"],
    "timeWindow": {
      "from": "2025-10-01T00:00:00Z",
      "to": "2025-10-29T00:00:00Z"
    },
    "products": ["registry.example.com/app:*"],
    "sboms": ["sbom:S-1001", "sbom:S-2004"]
  },
  "policySnapshotId": "policy-snap-42",
  "options": {
    "allowEmpty": false,
    "priority": "standard"
  }
}
Response 202
{
  "runId": "run-20251029-01",
  "status": "pending",
  "profileId": "prof-json-raw",
  "createdAt": "2025-10-29T12:12:11Z",
  "createdBy": "user:ops",
  "selectors": { "...": "..." },
  "links": {
    "self": "/api/export/runs/run-20251029-01",
    "events": "/api/export/runs/run-20251029-01/events"
  }
}
4.2 List runs
GET /api/export/runs?status=active&profileId=prof-json-raw&page=1&pageSize=10
Scopes: export:read
Returns latest runs with pagination. Each item includes summary counts, duration, and last event.
4.3 Get run status
GET /api/export/runs/{runId}
Scopes: export:read
Response fields:
| Field | Description | 
|---|---|
| status | pending,running,success,failed,canceled. | 
| progress | Object with adapters,bytesWritten,recordsProcessed. | 
| errorCode | Populated when status=failed(signing,distribution, etc). | 
| policySnapshotId | Returned for policy-aware profiles. | 
| distributions | List of available distribution descriptors (type, location, sha256, expiresAt). | 
4.4 Cancel a run
POST /api/export/runs/{runId}:cancel
Scopes: export:run
Body optional ({"reason": "Aborted due to incident INC-123"}). Returns 202 and pushes run.canceled event.
5. Events and telemetry
5.1 Server-sent events
GET /api/export/runs/{runId}/events
Scopes: export:read
Accept: text/event-stream
Event payload example:
event: run.progress
data: {"runId":"run-20251029-01","phase":"adapter","adapter":"json","records":1024,"bytes":7340032,"timestamp":"2025-10-29T12:13:15Z"}
Event types:
| Event | Meaning | 
|---|---|
| run.accepted | Planner accepted job and queued with Orchestrator. | 
| run.progress | Periodic updates with phase, adapter, counts. | 
| run.distribution | Distribution driver finished (includes descriptor). | 
| run.signed | Signing completed successfully. | 
| run.succeeded | Run marked success. | 
| run.failed | Run failed; payload includes errorCode. | 
| run.canceled | Run canceled; includes canceledBy. | 
SSE heartbeats (: ping) keep long-lived connections alive and should be ignored by clients.
5.2 Audit events
GET /api/export/runs/{runId}/events?format=audit returns the same event stream in newline-delimited JSON for offline ingestion.
6. Download endpoints
6.1 Bundle download
GET /api/export/runs/{runId}/download
Scopes: export:download
Streams the primary bundle (tarball, zip, or profile-specific layout). Headers:
- Content-Disposition: attachment; filename="export-run-20251029-01.tar.zst"
- X-Export-Digest: sha256:...
- X-Export-Size: 73482019
- X-Export-Encryption: age(when mirror encryption enabled)
Supports HTTP range requests for resume functionality. If no bundle exists yet, responds 409 with ERR_EXPORT_007.
6.2 Manifest download
GET /api/export/runs/{runId}/manifest
Scopes: export:download
Returns signed export.json. To fetch the detached signature, append ?signature=true.
6.3 Provenance download
GET /api/export/runs/{runId}/provenance
Scopes: export:download
Returns signed provenance.json. Supports ?signature=true. Provenance includes attestation subject digests, policy snapshot ids, adapter versions, and KMS key identifiers.
6.4 Distribution descriptors
GET /api/export/runs/{runId}/distributions
Scopes: export:read
Lists all registered distribution targets (HTTP, OCI, object storage). Each item includes type, location, sha256, sizeBytes, and expiresAt.
7. Webhook hand-off
Exports can notify external systems once a run succeeds by registering an HTTP webhook:
POST /api/export/webhooks
Scopes: export:profile:manage
Payload includes targetUrl, events (e.g., run.succeeded), and optional secret for HMAC signatures. Webhook deliveries sign payloads with X-Stella-Signature header (sha256=...). Retries follow exponential backoff with dead-letter capture in export_events.
8. Observability
- Metrics endpoint: /metrics(service-local) exposes Prometheus metrics listed in Architecture.
- Tracing: When traceparentheader is provided, worker spans join the calling trace.
- Run lookup by trace: Use GET /api/export/runs?traceId={id}when troubleshooting distributed traces.
9. Related documentation
- Export Center Overview
- Export Center Architecture
- Export Center Profiles
- Export Center CLI Guide (companion document)
- Aggregation-Only Contract reference
Imposed rule: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.