MediaJobStartedPayload media.job.started@1
Generated at from
openvibe-contracts v0.33.0 and
openvibe-sdk v0.5.0.
- Version
- 1.0.0
- Owner
- media
- Visibility
- first-party
- Status
- active
- Compatibility
- backward
- Decision
- ADR-004
- Schema
https://openvibe.network/contracts/events/payloads/media.job.started.v1.json
media.job.started v1 (OpenVibe.Media server/jobs/queue.js claim; server/events.js recordJob). The worker claimed the job and it is running (status running): attempts counts this attempt, started_at stays the first attempt's start. A job that is retried starts again and is announced again; error_code is still the previous attempt's until the job finishes. Emitted in the SQLite transaction that records the state change, so the event exists if and only if the change committed; it goes to OpenVibe.Events only (no app webhook). The payload is an event projection of the job (queue.jobEvent): its identity, state, counters, stable error_code, times and has_result. Deliberately left out, because events travel beyond the tenant: the tenant's params (object.split keeps whatever the caller sent), the caller's idempotency_key, the free-text error (raw handler or ffmpeg output), the result (thumbnail.regenerate's is the thumbnail URL, of a private VOD too), created_by/decided_by and owner_user_id (tenant-local user ids), and the handler's checkpoint, lease and request hash. GET the job with a tenant token for params/result/error (GET /api/v2/:app/jobs/:id with a token for app_id; has_result says whether there is a result to fetch). Times are ISO 8601 UTC (YYYY-MM-DDTHH:MM:SS.sssZ) or null. Jobs of developer-project sandbox tenants are not announced. Envelope: subject { type: job, id: <id> }, visibility internal, priority low, actor service:media.
Fields
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
id | string | yes | The job. GET /api/v2/:app/jobs/:id with a token for app_id answers the rest of it. |
|
app_id | string | yes | The tenant that owns the job: an app (live, tools…) or a developer project's production tenant (prj_…). |
|
object_id | string | null | yes | The object the job works on; null for a tenant-wide job (invariant.scan). |
|
type | string | yes | Job type: thumbnail.regenerate, invariant.scan, object.split or object.remux. |
|
status | const | yes | started carries the job's state, running. |
|
attempts | integer | yes | Attempts started so far, this one included. |
|
max_attempts | integer | yes |
| |
error_code | string | null | yes | Stable code of the last failure (media_unavailable, interrupted, cancelled, media.job.invalid…), or null when there was none or the handler gave no code. Never the message: GET the job for it. | |
cancel_requested | boolean | yes | Its owner asked a running job to stop. | |
has_result | const | yes | A running job has no result yet. |
|
run_after | null | yes | Cleared when the job is claimed. | |
decided_at | string | null | yes | When its owner approved it, or cancelled it while it was proposed or queued; null otherwise. |
|
created_at | string | yes |
| |
updated_at | string | yes |
| |
started_at | string | yes | The first attempt's start. |
|
finished_at | null | yes |
Examples
From the contract's own test fixtures: valid ones validate, rejected ones must fail.
Valid: cancel-asked-before-retry
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "running",
"attempts": 2,
"max_attempts": 3,
"error_code": "media_unavailable",
"cancel_requested": true,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:06:12.000Z",
"started_at": "2026-09-23T18:02:12.000Z",
"finished_at": null
}Valid: first-attempt
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "running",
"attempts": 1,
"max_attempts": 3,
"error_code": null,
"cancel_requested": false,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:02:12.000Z",
"started_at": "2026-09-23T18:02:12.000Z",
"finished_at": null
}Valid: second-attempt
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "running",
"attempts": 2,
"max_attempts": 3,
"error_code": "interrupted",
"cancel_requested": false,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:06:12.000Z",
"started_at": "2026-09-23T18:02:12.000Z",
"finished_at": null
}Rejected: no-start-time
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "running",
"attempts": 1,
"max_attempts": 3,
"error_code": null,
"cancel_requested": false,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:02:12.000Z",
"started_at": null,
"finished_at": null
}Rejected: offset-time
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "running",
"attempts": 1,
"max_attempts": 3,
"error_code": null,
"cancel_requested": false,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:02:12.000Z",
"started_at": "2026-09-23T20:02:12.000+02:00",
"finished_at": null
}Rejected: wrong-status
{
"id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
"app_id": "live",
"object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"type": "object.split",
"status": "started",
"attempts": 1,
"max_attempts": 3,
"error_code": null,
"cancel_requested": false,
"has_result": false,
"run_after": null,
"decided_at": null,
"created_at": "2026-09-23T18:02:11.000Z",
"updated_at": "2026-09-23T18:02:12.000Z",
"started_at": "2026-09-23T18:02:12.000Z",
"finished_at": null
}Validate
const contracts = require('openvibe-contracts');
contracts.validate('media.job.started@1', value); // { valid, errors: [{ path, message }] }