Anuva Web API Doc
Last refreshed: 2026-05-22
Purpose
This document tells the Anuva Python Server exactly how to integrate with the Anuva Web App worker API that is implemented in this repository.
It covers:
- What the Python Server should call
- How requests must be authenticated
- What each route expects and returns
- How uploads work
- What environment and schema setup is required
- The full setup sequence for both the Web App and the Python Server side
This document is implementation-oriented. It describes the currently implemented Web App contract under /api/worker/v1.
Scope
This API is for the external render worker only.
The Python Server should use it for:
- Discovering queued render jobs
- Claiming a job
- Downloading the render package
- Sending progress and heartbeats
- Uploading the final MP4 through signed S3 upload
- Completing, failing, or cancelling the job
The Python Server should not use it for:
- Creating presentations
- Generating scenes
- Editing presentation content
- Reading arbitrary Payload collections
Base URL And Auth
Base namespace:
Authentication:
Authorization: Bearer <ANUVA_WORKER_API_TOKEN>
Accept: application/json
Content-Type: application/json
If the token is missing or wrong, the API returns:
If the server is missing ANUVA_WORKER_API_TOKEN, the API returns:
Setup Steps
1. Set up the Anuva Web App
- Populate
.envfrom.env.example. - Set the normal app variables:
DATABASE_URIPAYLOAD_SECRETNEXT_PUBLIC_SERVER_URL- Set the storage variables required by worker asset download and upload signing:
S3_BUCKETS3_ENDPOINTS3_REGIONS3_ACCESS_KEY_IDS3_SECRET_ACCESS_KEY- Set the worker auth variable:
ANUVA_WORKER_API_TOKEN- Apply database schema changes before expecting the worker API or admin UI to work correctly.
- Start the app with
pnpm devor the equivalent production process.
2. Apply database migrations correctly
The worker API depends on these Payload collections existing in Postgres:
video_generation_jobsrender_workersrender_worker_reports
Important environment note:
- If
DATABASE_URIuses a Supabase pooler host,PAYLOAD_DB_PUSH=1is ignored. - In that case you must run explicit migrations against a direct Postgres connection.
Recommended sequence:
If your environment points at a pooler, switch DATABASE_URI to a direct Postgres connection first, run the migration, then switch back if needed.
3. Confirm worker API readiness
Before connecting the Python Server, confirm:
- The Web App is running on the expected base URL
ANUVA_WORKER_API_TOKENis present in the Web App environment- The worker routes respond with auth failures instead of internal server errors when called without a token
video_generation_jobsrows exist for test render jobs- Asset URLs and S3 signing work in the current environment
4. Configure the Python Server
Point the Python Server's configurable endpoint map to the production namespace:
GET /api/worker/v1/jobs/next
GET /api/worker/v1/jobs/available
GET /api/worker/v1/jobs/{jobId}
POST /api/worker/v1/jobs/{jobId}/claim
GET /api/worker/v1/jobs/{jobId}/package
PATCH /api/worker/v1/jobs/{jobId}/status
POST /api/worker/v1/workers/{workerId}/heartbeat
POST /api/worker/v1/jobs/{jobId}/uploads/signed
POST /api/worker/v1/jobs/{jobId}/uploads/confirm
POST /api/worker/v1/jobs/{jobId}/complete
POST /api/worker/v1/jobs/{jobId}/fail
POST /api/worker/v1/jobs/{jobId}/cancel
The Python Server should also be configured with:
- The Web App base URL
- The same bearer token value as
ANUVA_WORKER_API_TOKEN - A stable
workerId - Local workspace directories for package rewriting and Unity handoff
5. Verify the full worker flow
Recommended smoke test:
- Queue one
video_generation_jobsdocument inqueued - Call
GET /jobs/available - Claim the job
- Fetch the package
- Send one heartbeat
- Send
inProgressstatus - Request a signed upload URL
- Upload an MP4 to S3
- Confirm the upload
- Complete the job
- Confirm the presentation now points to the final video
End-to-end Flow
sequenceDiagram
participant Py as Python Server
participant Web as Anuva Web App
participant S3 as S3 Storage
participant Unity as Unity Renderer
Py->>Web: GET /api/worker/v1/jobs/available
Py->>Web: POST /api/worker/v1/jobs/{jobId}/claim
Py->>Web: GET /api/worker/v1/jobs/{jobId}/package
Py->>Web: POST /api/worker/v1/workers/{workerId}/heartbeat
Py->>Unity: render.start with local package URLs
Unity-->>Py: render.progress
Py->>Web: PATCH /api/worker/v1/jobs/{jobId}/status
Unity-->>Py: render.completed
Py->>Web: POST /api/worker/v1/jobs/{jobId}/uploads/signed
Py->>S3: PUT rendered MP4
Py->>Web: POST /api/worker/v1/jobs/{jobId}/uploads/confirm
Py->>Web: POST /api/worker/v1/jobs/{jobId}/complete
Implemented Route Catalog
GET /api/worker/v1/jobs/next
Returns the next queued render job or null.
Supported query params:
workerIdoptionaljobTypeoptional:preview | finalRenderpriorityoptional:standard | priority | fastestworkspaceIdoptionallimitoptional, though this route always returns at most one job
Example response:
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"status": "queued",
"progress": 0,
"message": null,
"workerId": null,
"claimedAt": null,
"updatedAt": "2026-05-22T10:00:00.000Z",
"errorCode": null,
"errorMessage": null,
"resultMediaId": null
}
GET /api/worker/v1/jobs/available
Returns a safe list of queued jobs for manual or hybrid claim flows.
Supported query params:
workerIdoptionaljobTypeoptionalpriorityoptionalworkspaceIdoptionallimitoptional, max50
Example response:
[
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"status": "queued",
"title": "Q2 Product Launch",
"createdAt": "2026-05-22T09:55:00.000Z",
"updatedAt": "2026-05-22T09:55:00.000Z",
"estimatedDurationSeconds": 75,
"assetCount": 14
}
]
GET /api/worker/v1/jobs/{jobId}
Returns one RenderJobSummary.
Use this for operator refresh or manual claim screens.
POST /api/worker/v1/jobs/{jobId}/claim
Claims a queued job atomically.
Request body:
Success response:
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"status": "claimed",
"progress": 0,
"message": "claimed by worker",
"workerId": "unity-worker-01",
"claimedAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"errorCode": null,
"errorMessage": null,
"resultMediaId": null
}
Conflict response when another worker already claimed it:
GET /api/worker/v1/jobs/{jobId}/package
Returns the full RenderJobPackage.
Important rule:
- This route returns
409if the job is stillqueued. - The Python Server should claim first, then request the package.
Example response shape:
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"config": {},
"assets": [
{
"assetId": "voice-001",
"kind": "audio",
"url": "https://storage.example.com/...",
"filename": "voice-001.mp3",
"mimeType": "audio/mpeg",
"checksum": "sha256...",
"required": true
}
],
"render": {
"resolution": "1080p",
"frameRate": 30,
"aspectRatio": "16:9",
"format": "mp4",
"watermark": false
}
}
Important package details:
configis the persistedPresentationConfigPublicsnapshot fromvideo_generation_jobs.configSnapshot.publicConfig.assets[*].filenameis always a safe basename, not a nested path.- Asset URLs are generated dynamically when the package is requested.
- Asset URLs should be treated as temporary signed download URLs.
PATCH /api/worker/v1/jobs/{jobId}/status
Updates a non-terminal or non-final status.
Request body:
Important status rules:
- API progress is
0.0..1.0 - Stored DB progress is
0..100 completedis not allowed here- Use
/completeto finalize the render
Supported worker-visible statuses:
queuedclaimedinProgressuploadinguploadFailedcompletedfailedcancelled
Valid transition rules:
queued -> queued | claimed | cancelledclaimed -> claimed | inProgress | failed | cancelledinProgress -> inProgress | uploading | uploadFailed | failed | cancelleduploading -> uploading | completed | uploadFailed | failed | cancelleduploadFailed -> uploadFailed | uploading | failed | cancelled- terminal states remain terminal
POST /api/worker/v1/workers/{workerId}/heartbeat
Stores the latest worker heartbeat in render_workers.
Request body:
{
"status": "ready",
"activeJobId": "job_001",
"details": {
"mode": "auto",
"unityReady": true,
"unityConnected": true
}
}
Response:
POST /api/worker/v1/jobs/{jobId}/uploads/signed
Creates a signed upload target for the final rendered MP4.
Request body:
{
"filename": "presentation-final.mp4",
"contentType": "video/mp4",
"fileSizeBytes": 12345678,
"metadata": {
"workerId": "unity-worker-01"
}
}
Response:
{
"uploadUrl": "https://storage.example.com/...",
"headers": {
"Content-Type": "video/mp4"
},
"storageKey": "render-results/job_001/presentation-final.mp4",
"uploadId": "uuid-value",
"publicUrl": "https://storage.example.com/bucket/render-results/job_001/presentation-final.mp4"
}
Current implementation note:
- V1 supports signed upload flow only.
- There is no direct multipart upload endpoint in the implemented Web App route set.
POST /api/worker/v1/jobs/{jobId}/uploads/confirm
Confirms that the upload succeeded and creates or reuses a media_assets record.
Request body:
{
"upload": {
"uploadUrl": "https://storage.example.com/...",
"headers": {
"Content-Type": "video/mp4"
},
"storageKey": "render-results/job_001/presentation-final.mp4",
"uploadId": "uuid-value"
},
"metadata": {
"workerId": "unity-worker-01",
"fileSizeBytes": 12345678
}
}
Response:
{
"mediaId": "media_001",
"url": "https://storage.example.com/bucket/render-results/job_001/presentation-final.mp4",
"filename": "presentation-final.mp4",
"mimeType": "video/mp4",
"fileSizeBytes": 12345678,
"metadata": {
"jobId": "job_001",
"workerId": "unity-worker-01"
}
}
Side effect:
- The job is moved to
uploadingandresultMediais set.
POST /api/worker/v1/jobs/{jobId}/complete
Marks the render job completed and updates the owning presentation's final video pointer.
Request body:
Response shape:
{
"job": {
"jobId": "job_001",
"status": "completed",
"progress": 1
},
"finalVideo": {
"mediaId": "media_001",
"url": "https://storage.example.com/bucket/render-results/job_001/presentation-final.mp4",
"filename": "presentation-final.mp4",
"mimeType": "video/mp4",
"fileSizeBytes": 12345678,
"metadata": {
"jobId": "job_001",
"workerId": "unity-worker-01"
}
}
}
POST /api/worker/v1/jobs/{jobId}/fail
Marks the render job failed and writes a diagnostic row to render_worker_reports.
Request body:
{
"errorCode": "UNITY_RENDER_FAILED",
"errorMessage": "Unity render returned non-zero exit status",
"diagnostics": {
"workerId": "unity-worker-01",
"unityLogPath": "C:\\\\logs\\\\unity.log"
}
}
POST /api/worker/v1/jobs/{jobId}/cancel
Marks a non-terminal render job cancelled.
Request body:
Data Contract Notes
Render job persistence
The worker API is backed by Payload collections:
video_generation_jobsrender_workersrender_worker_reports
Important fields in video_generation_jobs:
jobType:preview | finalRenderpriority:standard | priority | fasteststatus:queued | claimed | inProgress | uploading | uploadFailed | completed | failed | cancelledprogress: stored as0..100workerIdclaimedAtstatusMessageerrorCodeerrorMessageresultMediaconfigSnapshot.publicConfig
Package config
The Python Server should treat:
That object is the renderer-facing contract. The Python Server should serve it locally to Unity after rewriting any asset URLs it needs to local HTTP/HTTPS URLs.
Asset rules
The package assets list is a normalized manifest built from the config.
Rules:
- Every asset has a stable
assetId - Every asset has a safe basename in
filename - URLs are remote signed URLs from the Web App storage layer
- The worker may download and rewrite them into a deterministic local workspace
- Required assets must exist or package generation fails with
422
Error Model
Validation errors return:
Domain errors return:
Common error codes:
job_not_foundjob_already_claimedjob_not_claimedinvalid_status_transitionuse_complete_endpointjob_missing_presentationinvalid_render_job_packageunauthorized_worker_requestworker_auth_not_configuredworker_api_failed
Current Limitations
The Python Server should account for these current implementation details:
- Signed upload flow is the only supported upload path in V1.
GET /jobs/nextreturns the oldest queued job bycreatedAt; it does not claim automatically.- Asset URLs are signed at package-fetch time and should be treated as temporary.
- The Web App currently assumes S3 path-style object URLs for asset download and render-result upload.
- Schema changes require explicit migrations in pooler-backed Supabase environments.
Recommended Python Worker Sequence
Use this exact order:
GET /jobs/availableorGET /jobs/nextPOST /jobs/{jobId}/claimGET /jobs/{jobId}/packagePOST /workers/{workerId}/heartbeatPATCH /jobs/{jobId}/statustoinProgressPOST /jobs/{jobId}/uploads/signed- Upload the MP4 to S3 using the returned
uploadUrlandheaders POST /jobs/{jobId}/uploads/confirmPOST /jobs/{jobId}/complete
If rendering fails:
POST /jobs/{jobId}/fail
If the operator stops the job:
POST /jobs/{jobId}/cancel
If upload fails after rendering:
PATCH /jobs/{jobId}/statuswithuploadFailed- Retry signed upload and confirm
- Or fail the job if the upload cannot be recovered
File Map
These are the relevant implementation files in this repo:
docs/AnuvaPythonServer_SystemDescription.mdsrc/app/api/worker/v1/**src/lib/render-worker/contracts.tssrc/lib/render-worker/service.tssrc/lib/render-worker/auth.tssrc/lib/render-worker/http.tssrc/lib/render-worker/storage.tsdocs/core/WorkersAndJobs.mddocs/state/EnvAndServices.md