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

FieldTypeRequiredDescriptionConstraints
idstringyesThe job. GET /api/v2/:app/jobs/:id with a token for app_id answers the rest of it.
  • pattern ^mjob_[0-9A-HJKMNP-TV-Z]{26}$
app_idstringyesThe tenant that owns the job: an app (live, tools…) or a developer project's production tenant (prj_…).
  • minLength 1
  • maxLength 200
object_idstring | nullyesThe object the job works on; null for a tenant-wide job (invariant.scan).
  • maxLength 200
typestringyesJob type: thumbnail.regenerate, invariant.scan, object.split or object.remux.
  • pattern ^[a-z][a-z0-9]*\.[a-z0-9_.]+$
  • maxLength 80
statusconstyesstarted carries the job's state, running.
  • = "running"
attemptsintegeryesAttempts started so far, this one included.
  • minimum 1
max_attemptsintegeryes
  • minimum 1
  • maximum 10
error_codestring | nullyesStable 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_requestedbooleanyesIts owner asked a running job to stop.
has_resultconstyesA running job has no result yet.
  • = false
run_afternullyesCleared when the job is claimed.
decided_atstring | nullyesWhen its owner approved it, or cancelled it while it was proposed or queued; null otherwise.
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
created_atstringyes
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
updated_atstringyes
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
started_atstringyesThe first attempt's start.
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
finished_atnullyes

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 }] }