Spaces:
Sleeping
Sleeping
File size: 30,563 Bytes
4e11c88 a0319ec 4e11c88 46016e1 27329ef 4e11c88 4df94cd 439a2ff 4e11c88 46016e1 4e11c88 27329ef 4e11c88 27329ef 4e11c88 46016e1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | # API Contract V1
Status: capability map and transport contract. Slice 3C, Slice 3D, and the schema-v6 Phase 3E core are implemented, merged, and production-cutover complete; the Phase 3E.3 transactional-outbox runtime correction is not deployed until its later cutover verification. Phase 3F has not started.
## Protocol conventions
- Product API paths are `/api/v1/**`. Foundation probes remain `/api/health`, `/api/ready`, and `/api/version`.
- JSON contracts are strict Zod schemas exported once from `packages/contracts`; unknown keys fail with the canonical error envelope from `ERROR_MODEL.md`.
- UUID resource IDs are opaque. Unauthorized private resources are indistinguishable from missing resources (`404 RESOURCE_NOT_FOUND`).
- All unsafe cookie-authenticated requests apply `AUTH_SESSION_CSRF.md`.
- First-admin bootstrap is part of the verified Google callback transaction and
has no separate HTTP route. Admin role/status mutation enforces the
last-active-admin guard; emergency recovery is operator maintenance only.
- Important mutations require exactly one `Idempotency-Key` containing 1-128
ASCII characters from `A-Z a-z 0-9 . _ : -`. Missing, empty, multiple,
Unicode, whitespace, control, slash, backslash, quote, or oversized values
are `400 VALIDATION_ERROR`. The server hashes the key and canonical semantic
request; same key/same request replays the stored result and same key/different
request is `409 IDEMPOTENCY_CONFLICT`.
- Mutable resources expose an integer `version` and ETag. Updates require `If-Match` or `expectedVersion`; stale writes return `409 VERSION_CONFLICT` with current safe version, never silently overwrite.
- Lists use an opaque base64url cursor containing versioned canonical sort position, filter fingerprint, and ID tie-breaker. Default/max page sizes are 25/100. Sorting is deterministic; cursor/filter mismatch is `400 INVALID_CURSOR`.
- Times are ISO 8601 UTC strings at the HTTP boundary and epoch milliseconds in SQLite.
- IDs, not storage keys/paths/provider secrets, cross the API boundary.
### Slice 3C Project projection
Owner Project responses expose only `id`, `name`, nullable `description`,
`favorite`, `status`, ISO-8601 UTC `createdAt`/`updatedAt`, and positive
`version`. They never expose owner columns, retention/purge internals, storage
keys, or filesystem paths. Owner routes return `404 RESOURCE_NOT_FOUND` for a
different owner, including when the actor is an admin.
Slice 3C lists only `active` Projects at API-011 and only `soft_deleted`
Projects at API-062. Both use the authenticated opaque cursor contract and the
owner list order `favorite DESC, updated_at DESC, id DESC`. Slice 3C does not
implement purge scheduling/worker side effects despite the later-state schema
foundation.
### Slice 3D Asset projection
Owner Asset responses expose only the resource/project IDs, normalized original
filename, kind, declared/verified MIME, immutable byte size/checksum, bounded
technical metadata, ingestion/lifecycle status, safe failure code, timestamps,
and optimistic version. They never expose owner columns, storage/staging keys,
filesystem paths, reconciliation state, or capability hashes. Upload responses
expose the UploadSession ID, Asset ID, canonical received offset, declared
length, lifecycle status, expiry, and recommended chunk size only.
## Common errors
The stable code, status, retryability, message key, action, severity, and allowed
surface come only from `ERROR_MODEL.md`. Common mappings are
`400 VALIDATION_ERROR`/`INVALID_CURSOR`, `401 AUTHENTICATION_REQUIRED`,
`403 AUTHORIZATION_DENIED` or an account-status code,
`404 RESOURCE_NOT_FOUND`, `409 RESOURCE_STATE_CONFLICT`/`VERSION_CONFLICT`/
`IDEMPOTENCY_CONFLICT`, `413 PAYLOAD_TOO_LARGE`,
`415 UNSUPPORTED_MEDIA_TYPE`, `416 RANGE_NOT_SATISFIABLE`, typed 422 domain
codes, `429 QUOTA_EXCEEDED`/`RATE_LIMITED`/`PROVIDER_RATE_LIMITED`, typed
502/503 provider or storage codes, and `500 INTERNAL_ERROR`. Every response
contains safe `code`, localized `messageKey`, `suggestedAction`, `retryable`,
`requestId`, and optional allowlisted field details; never a stack or provider body.
## Capability map
Legend: `U` authenticated user/owner, `A` administrator, `P` public/operator probe, `O` OIDC provider callback. `Idem` means `Idempotency-Key` required. `Ver` means optimistic version required. “Audit” names the success/denial class; security denials may be sampled/rate-limited.
| ID | Method and path | Actor; request -> response | Errors | Idempotency/concurrency | Audit; transaction/side effect |
| ------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------------------------------------ | ------------------------------------------------------------ |
| API-001 | `GET /api/health` | P; none -> liveness/build | 500 | none/read-only | metric only; no tx |
| API-002 | `GET /api/ready` | P/operator; none -> DB/storage/migration readiness without secrets | 503 | none/read-only | operational log; no write tx |
| API-003 | `GET /api/version` | P; none -> build SHA/schema compatibility | 500 | cache-safe/read-only | metric only; no tx |
| API-004 | `GET /api/v1/auth/google/start` | browser; safe same-origin return path -> Google redirect | 400,429,503 | one-use OAuth transaction | login-start; short security-state write |
| API-005 | `GET /api/v1/auth/google/callback` | O/browser; code/state -> session cookie + validated return path; every failure -> fixed `303 /auth/error` without query disclosure | fixed safe redirect | state/nonce/PKCE one-use | login success/failure; provider denial consumes state; TX-01 |
| API-006 | `GET /api/v1/auth/session` | U/pending member; none -> safe user status/session expiry | 401 | read-only | none; session last-seen coalesced |
| API-007 | `GET /api/v1/auth/csrf` | U/pending member; none -> synchronizer token | 401 | no-store; per session | none; may rotate token in a guarded session write |
| API-008 | `POST /api/v1/auth/logout` | U/pending member; CSRF -> 204 | 401,403 | repeat succeeds | logout; TX-03 |
| API-009 | `GET /api/v1/auth/sessions` | U; cursor -> own safe session list | 401,400 | cursor/read-only | none |
| API-010 | `POST /api/v1/auth/sessions/revoke-all` | U; CSRF/confirm -> revoked count + current-session policy result | 401,403 | Idem | session revoke-all; TX-03 |
| API-011 | `GET /api/v1/projects` | U; cursor/status -> project summaries | 400,401 | cursor/read-only | none |
| API-012 | `POST /api/v1/projects` | U; name/description -> project | 400,409 | Idem | project.create; TX-04 |
| API-013 | `GET /api/v1/projects/:projectId` | U owner; ID -> project snapshot | 401,404 | ETag/read-only | none |
| API-014 | `PATCH /api/v1/projects/:projectId` | U owner; rename/favorite + expected version -> updated project | 400,401,404,409 | Idem + Ver | project.update; TX-05 |
| API-015 | `DELETE /api/v1/projects/:projectId` | U owner; expected version/confirm -> soft-deleted project | 401,404,409 | Idem + Ver | project.delete_requested; TX-06, later queues purge |
| API-016 | `POST /api/v1/projects/:projectId/restore` | U owner; expected version -> active project when policy permits | 401,404,409 | Idem + Ver | project.restore; TX-06 |
| API-017 | `POST /api/v1/projects/:projectId/uploads` | U owner; file metadata/size/checksum -> Asset + upload session | 400,404,409,413,415 | Idem; project Ver guard | upload.create; TX-07 |
| API-018 | `HEAD /api/v1/uploads/:uploadId` | U owner; ID -> offset/length/status headers | 401,404,409 | read-only authoritative offset | none |
| API-019 | `PATCH /api/v1/uploads/:uploadId` | U owner; offset + binary chunk -> new offset | 400,404,409,413,415 | offset/chunk checksum retry; Ver | upload.append metric; file then TX-08 |
| API-020 | `POST /api/v1/uploads/:uploadId/complete` | U owner; optional checksum -> asset/job | 404,409,415,422 | Idem + upload Ver | upload.complete; TX-09 |
| API-021 | `DELETE /api/v1/uploads/:uploadId` | U owner; ID -> 204 | 404 | repeat succeeds | upload.abort; TX-10 |
| API-022 | `GET /api/v1/projects/:projectId/assets` | U owner; cursor/media type/upload time/ingestion/lifecycle/original-filename search -> assets | 400,404 | cursor/read-only | none |
| API-023 | `GET /api/v1/assets/:assetId` | U owner; ID -> metadata/status | 401,404 | ETag/read-only | none |
| API-024 | `POST /api/v1/assets/:assetId/retry-ingestion` | U owner; failed active Asset/version -> same Asset + new ingestion attempt | 404,409 | Idem + Ver | asset.ingestion_retry; TX-10-style admission |
| API-025 | `DELETE /api/v1/assets/:assetId` | U owner; expected version -> soft-deleted lifecycle | 404,409 | Idem + Ver | asset.delete_requested; later TX-20 schedule |
| API-026 | `GET /api/v1/assets/:assetId/content` | U owner; Range/conditional headers -> bytes | 404,416,503 | immutable ETag/read-only | access metric; authorized range stream |
| API-027 | `POST /api/v1/assets/:assetId/delivery-capabilities` | U owner; operation -> short opaque capability | 404,409 | Idem; resource state guard | capability.issue; TX-23 |
| API-028 | `GET /api/v1/projects/:projectId/compositions` | U owner; cursor/status -> version summaries | 400,404 | cursor/read-only | none |
| API-029 | `POST /api/v1/projects/:projectId/compositions` | U owner; CompositionDocumentV1/parent/version -> immutable version | 400,404,409,422 | Idem + project Ver | composition.create; TX-11 |
| API-030 | `GET /api/v1/compositions/:compositionVersionId` | U owner; ID -> canonical document/validation | 404 | immutable ETag | none |
| API-031 | `POST /api/v1/compositions/:compositionVersionId/derive` | U owner; structured patch/edit intent -> new version | 400,404,409,422 | Idem + expected parent/project Ver | composition.derive; TX-11 |
| API-032 | `POST /api/v1/compositions/:compositionVersionId/validate` | U owner; ID -> durable validation result | 404,409,422 | Idem; immutable input | composition.validate; bounded job or deterministic service |
| API-033 | `POST /api/v1/projects/:projectId/generation-jobs` | U owner; brief/options/asset IDs -> Job | 400,404,409,422,503 | Idem + project Ver | generation.request; TX-12 |
| API-034 | `POST /api/v1/compositions/:compositionVersionId/regenerate` | U owner; structured change brief/options -> Job | 400,404,409,422,503 | Idem; immutable source + project Ver | generation.regenerate; TX-12 |
| API-035 | `POST /api/v1/compositions/:compositionVersionId/preview` | U owner; ID -> preview artifact/status | 404,409,422 | Idem; immutable version | preview.request/materialize; TX-12/TX-17 |
| API-036 | `GET /api/v1/previews/:previewArtifactId` | U owner; ID -> artifact metadata/player bootstrap | 404,409,503 | immutable ETag/read-only | preview.access; authorized bootstrap |
| API-037 | `GET /api/v1/previews/:previewArtifactId/content` | U owner or scoped capability; Range -> immutable artifact bytes | 404,416,503 | ETag/read-only | safe access metric; range stream |
| API-038 | `POST /api/v1/compositions/:compositionVersionId/render-jobs` | U owner; output preset -> Job | 400,404,409,422,503 | Idem; immutable composition | render.request; TX-12 |
| API-039 | `GET /api/v1/jobs` | U; cursor/type/status/project -> summaries | 400 | cursor/read-only | none |
| API-040 | `GET /api/v1/jobs/:jobId` | U owner; ID -> durable snapshot | 404 | ETag/read-only | none |
| API-041 | `GET /api/v1/jobs/:jobId/steps` | U owner; cursor -> safe step summaries | 400,404 | cursor/read-only | none |
| API-042 | `GET /api/v1/jobs/:jobId/event-history` | U owner; cursor -> durable events | 400,404 | cursor/read-only | none |
| API-043 | `GET /api/v1/jobs/:jobId/events` | U owner; Last-Event-ID -> SSE | 400,404,409 | durable replay; no mutation | connection metric; no tx beyond reads |
| API-044 | `POST /api/v1/jobs/:jobId/cancel` | U owner; cancellable job/version -> snapshot | 404,409 | Idem + Ver | job.cancel_requested; TX-16 |
| API-045 | `GET /api/v1/projects/:projectId/outputs` | U owner; cursor/state -> output summaries | 400,404 | cursor/read-only | none |
| API-046 | `GET /api/v1/outputs/:outputId` | U owner; ID -> immutable evidence + lifecycle | 404 | ETag/read-only | none |
| API-047 | `GET /api/v1/outputs/:outputId/content` | U owner/capability; Range -> video bytes | 404,416,503 | immutable ETag | output.download/stream metric; no write tx |
| API-048 | `POST /api/v1/outputs/:outputId/delivery-capabilities` | U owner; download/stream -> opaque capability | 404,409 | Idem | capability.issue; TX-23 |
| API-049 | `DELETE /api/v1/outputs/:outputId` | U owner; expected state version -> purge-scheduled | 404,409 | Idem + Ver | output.delete_requested; TX-20 |
| API-050 | `GET /api/v1/settings/profile` | U; none -> safe profile/preferences | 401 | ETag/read-only | none |
| API-051 | `PATCH /api/v1/settings/profile` | U; display/preferences/version -> profile | 400,409 | Idem + Ver | user.profile_update; short user tx |
| API-052 | `GET /api/v1/settings/provider-status` | U; none -> enabled capability health (no reference names) | 401 | short cache/read-only | none |
| API-053 | `GET /api/v1/admin/users` | A; cursor/status/search -> safe users | 400,403 | cursor/read-only | admin.read_users |
| API-054 | `PATCH /api/v1/admin/users/:userId` | A; role/status/version -> user; reject last-active-admin lockout | 400,403,404,409 | Idem + Ver | admin.user_update; TX-02 |
| API-055 | `GET /api/v1/admin/jobs` | A; cursor/type/status/lease -> redacted operational summaries | 400,403 | cursor/read-only | admin.read_jobs |
| API-056 | `POST /api/v1/admin/jobs/:jobId/reconcile` | A; reason/version -> redacted reconciliation intent | 403,404,409 | Idem + Ver | admin.job_reconcile; TX-13/outbox |
| API-057 | `GET /api/v1/admin/provider-credentials` | A; cursor -> reference names/status/health | 403 | cursor/read-only | admin.read_provider_refs |
| API-058 | `PUT /api/v1/admin/provider-credentials/:referenceId` | A; provider/purpose/env-name/status/version -> reference | 400,403,409 | Idem + Ver | admin.provider_ref_update; never writes HF secret |
| API-059 | `GET /api/v1/admin/audit-events` | A; cursor/actor/action/resource/time -> redacted events | 400,403 | cursor/read-only | admin.read_audit |
| API-060 | `GET /api/v1/admin/operations` | A; none -> queue/lease/outbox/storage/backup health summary | 403,503 | read-only snapshot | admin.read_operations |
| API-062 | `GET /api/v1/trash/projects` | U; cursor -> own soft-deleted/purge-scheduled Projects | 400,401 | cursor/read-only | none |
| API-063 | `GET /api/v1/projects/:projectId/compositions/current` | U owner; project ID -> selected immutable version or null | 404 | project ETag/read-only | none |
| API-064 | `POST /api/v1/outputs/:outputId/pin` | U owner; state version -> pinned state | 404,409 | Idem + Ver | output.pin; guarded output-state tx |
| API-065 | `POST /api/v1/outputs/:outputId/unpin` | U owner; state version -> unpinned state | 404,409 | Idem + Ver | output.unpin; guarded output-state tx |
| API-066 | `GET /api/v1/admin/users/pending` | A; cursor -> pending members | 400,403 | cursor/read-only | admin.read_pending_users |
| API-067 | `POST /api/v1/admin/provider-credentials/:referenceId/health-check` | A; reference/version -> safe health result/job | 403,404,409,503 | Idem + Ver | admin.provider_health_check; bounded outbox/job intent |
| API-068 | `GET /api/v1/admin/quota-policies` | A; cursor/scope -> versioned policies | 400,403 | cursor/read-only | admin.read_quota_policies |
| API-069 | `POST /api/v1/admin/quota-policies` | A; scope/policy/effective interval -> new policy version | 400,403,409 | Idem + expected prior Ver | admin.quota_policy_create; quota/audit tx |
| API-070 | `POST /api/v1/jobs/:jobId/retry` | U owner; retryable terminal job -> new Job | 404,409,503 | Idem; immutable original request | job.retry; TX-12, never reopens terminal row |
| API-071 | `GET /api/v1/admin/jobs/:jobId` | A; ID -> redacted state/step/code/timestamps/lease diagnostics | 403,404 | ETag/read-only | admin.read_job_diagnostics; no owner SSE/content |
| API-072 | `GET /api/v1/outputs/:outputId/download` | U owner or download-scoped capability; conditional request -> attachment bytes | 404,416,503 | immutable ETag/read-only | output.download metric; authorized Range stream |
## Cursor shape
The opaque cursor encodes versioned canonical sort position, filter hash, and
UUID tie-breaker as base64url JSON plus HMAC-SHA256 under an HKDF-derived app
key. The implemented admin-user cursor is `{v:1, sort:{createdAt,id},
filterHash}` and binds normalized status/search. It never contains email,
display name, secret values, storage keys, provider operation IDs, or token
material. Invalid encoding/MAC/version/key/filter is `400 INVALID_CURSOR`;
default/max page sizes are 25/100.
## Contract ownership and evolution
Request, response, event, cursor, error, and composition schemas live in `packages/contracts` and are imported by both Fastify and React. API v1 additions are backward compatible; breaking shape/meaning requires a new API or schema version. OpenAPI may be generated from the canonical Zod contracts in Phase 3, but generated output is not a competing source of truth.
The capability map contains 72 sequential capabilities. Render cancellation and
retry use only the generic Job commands; no parallel render-specific command
routes exist. Owner-facing Project, CompositionVersion, Job history/status and
SSE routes are owner-only. Administrators inspect Jobs only through the redacted
admin list/detail/command surfaces, which exclude prompts, private bytes,
storage paths/keys, raw provider payloads, and owner SSE.
|