Align AOC tasks for Excititor and Concelier
This commit is contained in:
		@@ -1,337 +1,337 @@
 | 
			
		||||
# 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](architecture.md) and [Profiles](profiles.md) 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:manage` for profile CRUD.
 | 
			
		||||
  - `export:run` to submit and cancel runs.
 | 
			
		||||
  - `export:read` to list and inspect runs.
 | 
			
		||||
  - `export:download` for bundle downloads and manifests.
 | 
			
		||||
- **Tenant context:** Provide `X-Stella-Tenant` when 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`, and `X-Stella-Quota-Reset`. Exceeding quotas returns `429 Too Many Requests` with `ERR_EXPORT_QUOTA`.
 | 
			
		||||
- **Content negotiation:** Requests and responses use `application/json; charset=utf-8` unless otherwise stated. Downloads stream binary content with profile-specific media types.
 | 
			
		||||
- **SSE:** Event streams set `Content-Type: text/event-stream` and keep connections alive with comment heartbeats every 15 seconds.
 | 
			
		||||
 | 
			
		||||
## 2. Error model
 | 
			
		||||
 | 
			
		||||
Errors follow standard HTTP codes with structured payloads:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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 | `details` enumerates 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 `retryAfterSeconds` in `details`. |
 | 
			
		||||
| `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 | `details` lists 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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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](architecture.md#observability).
 | 
			
		||||
- **Tracing:** When `traceparent` header 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](overview.md)
 | 
			
		||||
- [Export Center Architecture](architecture.md)
 | 
			
		||||
- [Export Center Profiles](profiles.md)
 | 
			
		||||
- [Export Center CLI Guide](cli.md) *(companion document)*
 | 
			
		||||
- [Aggregation-Only Contract reference](../../ingestion/aggregation-only-contract.md)
 | 
			
		||||
 | 
			
		||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
 | 
			
		||||
# 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](architecture.md) and [Profiles](profiles.md) 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:manage` for profile CRUD.
 | 
			
		||||
  - `export:run` to submit and cancel runs.
 | 
			
		||||
  - `export:read` to list and inspect runs.
 | 
			
		||||
  - `export:download` for bundle downloads and manifests.
 | 
			
		||||
- **Tenant context:** Provide `X-Stella-Tenant` when 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`, and `X-Stella-Quota-Reset`. Exceeding quotas returns `429 Too Many Requests` with `ERR_EXPORT_QUOTA`.
 | 
			
		||||
- **Content negotiation:** Requests and responses use `application/json; charset=utf-8` unless otherwise stated. Downloads stream binary content with profile-specific media types.
 | 
			
		||||
- **SSE:** Event streams set `Content-Type: text/event-stream` and keep connections alive with comment heartbeats every 15 seconds.
 | 
			
		||||
 | 
			
		||||
## 2. Error model
 | 
			
		||||
 | 
			
		||||
Errors follow standard HTTP codes with structured payloads:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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 | `details` enumerates 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 `retryAfterSeconds` in `details`. |
 | 
			
		||||
| `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 | `details` lists 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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "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](architecture.md#observability).
 | 
			
		||||
- **Tracing:** When `traceparent` header 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](overview.md)
 | 
			
		||||
- [Export Center Architecture](architecture.md)
 | 
			
		||||
- [Export Center Profiles](profiles.md)
 | 
			
		||||
- [Export Center CLI Guide](cli.md) *(companion document)*
 | 
			
		||||
- [Aggregation-Only Contract reference](../../ingestion/aggregation-only-contract.md)
 | 
			
		||||
 | 
			
		||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user