MediaJobQueuedPayload media.job.queued@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.queued.v1.json

media.job.queued v1 (OpenVibe.Media server/jobs/queue.js enqueue, approve; server/events.js recordJob). A job was accepted and queued: a new job (POST /api/v2/:app/jobs), or a proposal its owner approved (decided_at). An Idempotency-Key replay, or a request joined to an identical job that is still active, creates no job and no event. A failed attempt that is queued again is media.job.retrying, not this. 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
statusconstyes
  • = "queued"
attemptsintegeryesAttempts started so far.
  • minimum 0
max_attemptsintegeryes
  • minimum 1
  • maximum 10
error_codenullyes
cancel_requestedbooleanyesIts owner asked a running job to stop.
has_resultconstyesA job that never ran has no result.
  • = false
run_afterstring | nullyesA queued job waits until then (retry backoff); 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
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_atnullyes
finished_atnullyes

Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

Valid: approved-proposal
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "queued",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": "2026-09-23T18:10:40.000Z",
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:10:40.000Z",
  "started_at": null,
  "finished_at": null
}
Valid: tenant-wide-scan
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": null,
  "type": "invariant.scan",
  "status": "queued",
  "attempts": 0,
  "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:11.000Z",
  "started_at": null,
  "finished_at": null
}
Valid: thumbnail
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "thumbnail.regenerate",
  "status": "queued",
  "attempts": 0,
  "max_attempts": 2,
  "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:11.000Z",
  "started_at": null,
  "finished_at": null
}
Rejected: already-ran
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "queued",
  "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:11.000Z",
  "started_at": "2026-09-23T18:02:12.000Z",
  "finished_at": null
}
Rejected: carries-idempotency-key
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "queued",
  "attempts": 0,
  "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:11.000Z",
  "started_at": null,
  "finished_at": null,
  "idempotency_key": "thumb-4211-2026-09-23"
}
Rejected: carries-owner-user
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "thumbnail.regenerate",
  "status": "queued",
  "attempts": 0,
  "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:11.000Z",
  "started_at": null,
  "finished_at": null,
  "created_by": "app:live:user:57",
  "owner_user_id": 57
}
Rejected: sqlite-timestamps
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "queued",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23 18:02:11",
  "updated_at": "2026-09-23 18:02:11",
  "started_at": null,
  "finished_at": null
}

Validate

const contracts = require('openvibe-contracts');
contracts.validate('media.job.queued@1', value);   // { valid, errors: [{ path, message }] }