Oloka-Video-Dev / docs /architecture /API_CONTRACT_V1.md
phucsd's picture
Deploy GitHub commit 7c8ae5c8c2f82880aab7364acaf21b26c8db01fc
a0319ec verified
|
Raw History Blame Contribute Delete
30.6 kB

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.