Jobs API
All long-running processes in ViralSync are modelled as Jobs. This API gives you a unified interface to list, inspect, stream progress, cancel, and delete jobs of any type.
Job object
Every endpoint in this API returns Job objects. Here is the full shape:
{
"jobId": "job_a1b2c3d4e5f6",
"type": "render",
"status": "running",
"phase": "rendering",
"progress": 0.62,
"parentJobId": null,
"outputUrl": null,
"error": null,
"mediaData": null,
"payload": {
"projectId": "proj_abc123",
"movie": { /* the original movie JSON */ }
},
"children": {
"total": 3,
"succeeded": 1,
"running": 1,
"queued": 1,
"failed": 0
},
"createdAt": "2026-04-02T10:00:00.000Z",
"updatedAt": "2026-04-02T10:00:08.432Z"
}
Job status values
| Status | Description |
|---|---|
queued | Job is waiting to be picked up by a worker. |
running | Job is actively being processed. |
waiting | Parent job is waiting for child jobs to complete. |
done | Job completed successfully. outputUrl is populated. |
failed | Job ended with an error. error field contains the message. |
cancelled | Job was cancelled via POST /v1/jobs/{jobId}/cancel. |
Render-specific phase values
For jobs with type: "render", the phase field provides finer-grained status within the running/waiting state:
| Phase | Description |
|---|---|
planning | Analysing the movie, creating the render plan and child jobs. |
dispatching | Sending scene jobs to render workers. |
waiting | Waiting for all scene jobs to complete. |
rendering | Scene jobs are actively rendering (reported per-scene). |
assembling | Concatenating rendered scenes into the final video. |
uploading | Uploading the final video to Cloudflare R2. |
GET /v1/jobs
List all jobs for your account, ordered by createdAt descending (most recent first).
Endpoint: https://api.viralsync.io/v1/jobs
Authentication: x-api-key header
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer 1–100 | 25 | Number of jobs to return per page. |
cursor | string | — | Opaque cursor from the previous response's nextCursor field. Omit for the first page. |
type | string | — | Filter by job type. Example: render, publish, workflow. |
status | string | — | Filter by status. Example: running, done, failed. |
Example
curl -s \
-H "x-api-key: vs_prod_YOUR_API_KEY" \
"https://api.viralsync.io/v1/jobs?limit=10&type=render"
Response
{
"jobs": [
{
"jobId": "job_a1b2c3d4e5f6",
"type": "render",
"status": "done",
"progress": 1.0,
"outputUrl": "https://r2.viralsync.io/outputs/job_a1b2c3d4e5f6/output.mp4",
"createdAt": "2026-04-02T10:00:00.000Z",
"updatedAt": "2026-04-02T10:00:42.000Z"
}
],
"nextCursor": "eyJsYXN0SWQiOiJqb2JfYTFiMmMzZDRlNWY2In0"
}
Pass nextCursor as the cursor parameter in your next request to retrieve the next page. When nextCursor is null, there are no more results.
GET /v1/jobs/{jobId}
Retrieve the full details of a specific job.
Endpoint: https://api.viralsync.io/v1/jobs/{jobId}
Authentication: x-api-key header
Example
curl -s \
-H "x-api-key: vs_prod_YOUR_API_KEY" \
https://api.viralsync.io/v1/jobs/job_a1b2c3d4e5f6
Response — job in progress
{
"jobId": "job_a1b2c3d4e5f6",
"type": "render",
"status": "running",
"phase": "rendering",
"progress": 0.62,
"parentJobId": null,
"outputUrl": null,
"error": null,
"children": {
"total": 3,
"succeeded": 1,
"running": 1,
"queued": 1,
"failed": 0
},
"payload": {
"projectId": "proj_abc123"
},
"createdAt": "2026-04-02T10:00:00.000Z",
"updatedAt": "2026-04-02T10:00:08.432Z"
}
Response — completed job
{
"jobId": "job_a1b2c3d4e5f6",
"type": "render",
"status": "done",
"phase": null,
"progress": 1.0,
"outputUrl": "https://r2.viralsync.io/outputs/job_a1b2c3d4e5f6/output.mp4",
"error": null,
"mediaData": {
"duration": 10.0,
"width": 1920,
"height": 1080,
"fileSize": 8432156
},
"children": {
"total": 3,
"succeeded": 3,
"running": 0,
"queued": 0,
"failed": 0
},
"createdAt": "2026-04-02T10:00:00.000Z",
"updatedAt": "2026-04-02T10:00:42.000Z"
}
Error responses
| Status | Code | Description |
|---|---|---|
401 | UNAUTHORIZED | Invalid or missing API key. |
404 | JOB_NOT_FOUND | The job does not exist or belongs to another account. |
GET /v1/jobs/{jobId}/progress
Stream real-time progress updates for a job using Server-Sent Events (SSE).
This endpoint is a streaming connection, not a one-shot request. The server keeps the connection open and pushes updates as the job progresses. Use this for real-time UI updates. For simple integrations, polling GET /v1/jobs/{jobId} every 3–5 seconds is sufficient.
Endpoint: https://api.viralsync.io/v1/jobs/{jobId}/progress
Authentication: x-api-key header
Response type: text/event-stream
The connection stays open until the job reaches a terminal state (done, failed, cancelled) or 30 minutes elapses (after which you should reconnect).
SSE event format
Each event is a JSON-encoded progress snapshot:
data: {"jobId":"job_a1b2c3d4e5f6","status":"running","phase":"rendering","progress":0.46,"children":{"total":3,"succeeded":1,"running":1,"queued":1,"failed":0},"outputUrl":null}
data: {"jobId":"job_a1b2c3d4e5f6","status":"done","phase":null,"progress":1.0,"outputUrl":"https://r2.viralsync.io/outputs/job_a1b2c3d4e5f6/output.mp4"}
Example — curl (stream to terminal)
curl -N \
-H "x-api-key: vs_prod_YOUR_API_KEY" \
https://api.viralsync.io/v1/jobs/job_a1b2c3d4e5f6/progress
The -N flag disables curl's output buffering so events print as they arrive.
Example — JavaScript (browser / Node.js)
const source = new EventSource(
'https://api.viralsync.io/v1/jobs/job_a1b2c3d4e5f6/progress',
{ headers: { 'x-api-key': 'vs_prod_YOUR_API_KEY' } }
);
source.onmessage = (event) => {
const update = JSON.parse(event.data);
console.log(`Progress: ${Math.round(update.progress * 100)}% — ${update.status}`);
if (update.status === 'done') {
console.log('Video ready:', update.outputUrl);
source.close();
}
if (update.status === 'failed') {
console.error('Render failed');
source.close();
}
};
source.onerror = () => {
// Reconnect after a short delay
source.close();
};
Example — Node.js with eventsource package
npm install eventsource
import EventSource from 'eventsource';
const jobId = 'job_a1b2c3d4e5f6';
const source = new EventSource(
`https://api.viralsync.io/v1/jobs/${jobId}/progress`,
{ headers: { 'x-api-key': process.env.VIRALSYNC_API_KEY } }
);
source.onmessage = (event) => {
const update = JSON.parse(event.data);
console.log(`[${update.status}] ${Math.round((update.progress ?? 0) * 100)}%`);
if (['done', 'failed', 'cancelled'].includes(update.status)) {
source.close();
if (update.status === 'done') console.log('Output:', update.outputUrl);
}
};
POST /v1/jobs/{jobId}/cancel
Cancel a running or queued job.
Endpoint: https://api.viralsync.io/v1/jobs/{jobId}/cancel
Authentication: x-api-key header
Body: None required
Example
curl -s -X POST \
-H "x-api-key: vs_prod_YOUR_API_KEY" \
https://api.viralsync.io/v1/jobs/job_a1b2c3d4e5f6/cancel
Response
Returns the updated job object with status: "cancelled".
{
"jobId": "job_a1b2c3d4e5f6",
"status": "cancelled",
"progress": 0.31,
"outputUrl": null
}
Error responses
| Status | Code | Description |
|---|---|---|
404 | JOB_NOT_FOUND | Job does not exist or belongs to another account. |
409 | INVALID_TRANSITION | Job is already in a terminal state (done, failed, cancelled). Check status before cancelling. |
Cancelled jobs do not consume render minutes. See Render Minutes →
DELETE /v1/jobs/{jobId}
Delete a job and its associated data. Only terminal jobs (done, failed, cancelled) can be deleted.
Endpoint: https://api.viralsync.io/v1/jobs/{jobId}
Method: DELETE
Authentication: x-api-key header
Example
curl -s -X DELETE \
-H "x-api-key: vs_prod_YOUR_API_KEY" \
https://api.viralsync.io/v1/jobs/job_a1b2c3d4e5f6
Response
204 No Content on success.
Error responses
| Status | Code | Description |
|---|---|---|
400 | JOB_CANNOT_BE_DELETED | Job is still running. Cancel it first, then delete. |
404 | JOB_NOT_FOUND | Job does not exist. |
Related
- Render API — submitting a render job
- Error Reference — all error codes
- Webhooks — get notified when a job completes without polling
- Job Model Concepts — how parent/child jobs work