# Export Center Gateway Contract (draft v0.9) Scope: proxy Export Center APIs through the Web gateway with tenant scoping, deterministic responses, sealed-mode readiness, and offline-friendly signed URL handling. ## Security / headers - `Authorization: DPoP `, `DPoP: ` - `X-StellaOps-Tenant: ` (required) - `X-StellaOps-Project: ` (optional) - `Idempotency-Key: ` (recommended for POST) - `Accept: application/json` (or `text/event-stream` for SSE) - Scopes (proposal): `export:read` for GET, `export:write` for POST. ## Endpoints - `GET /export-center/profiles` — list export profiles (tenant-scoped). - `POST /export-center/runs` — start an export run. - `GET /export-center/runs/{runId}` — run status + outputs. - `GET /export-center/runs/{runId}/events` — SSE progress stream. - `GET /export-center/distributions/{id}` — signed URLs for OCI/object storage distribution. ## POST /export-center/runs (request) ```jsonc { "profileId": "export-profile::tenant-default::daily-vex", "targets": ["vex", "advisory", "policy"], "formats": ["json", "ndjson"], "distribution": { "type": "oci", "ref": "registry.local/exports/daily", "signing": { "enabled": true, "keyRef": "k8s://secrets/eks/oci-signer" } }, "retentionDays": 30, "encryption": { "enabled": true, "kmsKey": "kms://tenant-default/key1" }, "priority": "normal" } ``` ## 202 Accepted ```jsonc { "runId": "export-run::tenant-default::2025-12-06::0003", "status": "queued", "estimateSeconds": 420, "links": { "status": "/export-center/runs/export-run::tenant-default::2025-12-06::0003", "events": "/export-center/runs/export-run::tenant-default::2025-12-06::0003/events" }, "retryAfter": 5 } ``` ## GET /export-center/runs/{runId} ```jsonc { "runId": "export-run::tenant-default::2025-12-06::0003", "status": "running", "profileId": "export-profile::tenant-default::daily-vex", "startedAt": "2025-12-06T10:00:00Z", "outputs": [ { "type": "manifest", "format": "json", "url": "https://exports.local/tenant-default/0003/manifest.json?sig=...", "sha256": "sha256:c0ffee...", "dsseUrl": "https://exports.local/tenant-default/0003/manifest.dsse?sig=...", "expiresAt": "2025-12-06T16:00:00Z" } ], "progress": { "percent": 35, "itemsCompleted": 70, "itemsTotal": 200 }, "errors": [] } ``` ## SSE events - `started`: `{ runId, status }` - `progress`: `{ runId, percent, itemsCompleted, itemsTotal }` - `artifact_ready`: `{ runId, type, id, url, sha256, format }` - `completed`: `{ runId, status: "succeeded", manifestUrl, manifestDsseUrl? }` - `failed`: `{ runId, status: "failed", code, message, retryAfterSeconds? }` ## Distributions - `GET /export-center/distributions/{id}` returns signed URLs, expiry, checksum, and optional DSSE envelope reference. - Response headers: `Cache-Control: private, max-age=60, stale-if-error=300`; `ETag` over sorted payload. - Signed URL rels: `self`, `alternate` (DSSE), `bundle` when tar/zip produced. ## Determinism & limits - Max request body 256 KiB; max targets 50; max outputs 1000 assets/export; max bundle size 500 MiB compressed. - Default job timeout 60 minutes; idle SSE timeout 60s; client backoff `1s,2s,4s,8s` capped at 30s; honor `Retry-After`. - Ordering: manifest items sorted `(type asc, id asc, format asc, url asc)`. - Timestamps: ISO-8601 UTC; stable SHA-256 hashes only. ## Error codes (proposal) - `ERR_EXPORT_PROFILE_NOT_FOUND` - `ERR_EXPORT_REQUEST_INVALID` - `ERR_EXPORT_TOO_LARGE` - `ERR_EXPORT_RATE_LIMIT` - `ERR_EXPORT_DISTRIBUTION_FAILED` - `ERR_EXPORT_EXPIRED` ## Samples - Run request/response: see blocks above. - Status/manifest/events: reuse Console manifest sample (`docs/api/console/samples/console-export-manifest.json`) until Export Center publishes dedicated samples. ## Outstanding for sign-off - Final scope/limit numbers (targets, bundle cap, timeouts). - Whether DSSE is mandatory for sealed tenants. - Distribution signing rules (key source, validity duration) and retention defaults.