ToolsJob tools.job@1

Generated at from openvibe-contracts v0.33.0 and openvibe-sdk v0.5.0.

Version
1.0.0
Owner
tools
Visibility
public
Status
active
Compatibility
backward
Decision
ADR-027
Schema
https://openvibe.network/contracts/tools/job.v1.json

A Tools job as the jobs API answers it (OpenVibe.Tools apps/_shared/jobs/http.js, system.view; ADR-027): POST /api/v1/jobs (202 + Location, or 200 + Idempotent-Replayed: true), GET and DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT and DELETE /api/v1/jobs/:id/references/:ref, the data of every SSE event on GET /api/v1/jobs/:id/events, and the job member of tools.run@1. A job is visible only to its owner (a person, a service/app principal, or one browser session); anyone else gets the same 404 tools.job.not_found as for a job that does not exist. result is null until the job succeeded; each result file is downloaded from its url (GET /api/v1/jobs/:id/files/:n, ?inline=1 for previews) and is stored as a private OpenVibe.Media object (storage media) or on the satellite until the job expires (storage local). expires_at is null while a reference keeps the result. error is problem+json: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), with server paths removed.

Fields

FieldTypeRequiredDescriptionConstraints
idstringyes
  • pattern ^job_[0-9A-HJKMNP-TV-Z]{26}$
objectconstyes
  • = "tools.job"
servicestringyesThe Tools satellite that runs the job (img, audio, docs…).
  • pattern ^[a-z][a-z0-9-]{1,39}$
toolstringThe tool (tools.tool@1 id) whose run created the job, when it came through POST /api/v1/tools/{id}/run. Absent for a job submitted to POST /api/v1/jobs directly.
  • pattern ^[a-z][a-z0-9-]{0,39}$
typestringyesJob type, e.g. img.process.
  • pattern ^[a-z][a-z0-9]*\.[a-z0-9_.]+$
  • maxLength 80
type_versionintegeryes
  • minimum 1
stateenumyesTerminal: succeeded, failed, cancelled.
  • one of "queued", "running", "succeeded", "failed", "cancelled"
progressobjectyes
  • no other fields
progress.percentnumber | nullyes
  • minimum 0
  • maximum 100
progress.messagestring | nullyes
  • maxLength 500
attemptsintegeryes
  • minimum 0
max_attemptsintegeryes
  • minimum 1
cancel_requestedbooleanyesDELETE asked a running job to stop; it ends cancelled.
created_atstringyes
  • format date-time
started_atstring | nullyes
  • format date-time
finished_atstring | nullyes
  • format date-time
expires_atstring | nullyesWhen the job, its events and its result files are pruned; null while a reference keeps the result.
  • format date-time
resultobject | nullyes
errorone ofyes
retryablebooleanyesRetrying may succeed (a timeout, a restart); POST links.retry makes a new job.
retry_ofstring | nullyesThe failed job this one retries.
  • pattern ^job_[0-9A-HJKMNP-TV-Z]{26}$
retried_bystring | nullyesThe job that retried this failed one; asking again returns it.
  • pattern ^job_[0-9A-HJKMNP-TV-Z]{26}$
referencesarray of objectyesWhat keeps the result (PUT /api/v1/jobs/:id/references/:ref), e.g. community:paste:p_123.
  • maxItems 50
  • items: no other fields
references[].refstringyes
  • pattern ^[a-z][a-z0-9_-]*(?::[A-Za-z0-9_.-]+){1,4}$
  • maxLength 200
references[].created_atstringyes
  • format date-time
linksobjectyes
  • no other fields
links.selfstringyes
  • format uri-reference
links.eventsstringyes
  • format uri-reference
links.cancelstring | nullyesDELETE it while the job is queued or running.
  • format uri-reference
links.retrystring | nullyesPOST it when the job failed.
  • format uri-reference
links.retried_bystring | nullyes
  • format uri-reference

Examples

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

Valid: failed-retryable
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "audio",
  "type": "audio.process",
  "type_version": 1,
  "state": "failed",
  "progress": {
    "percent": 42.5,
    "message": "Encoding"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:15:01.000Z",
  "expires_at": "2026-09-23T10:00:00.000Z",
  "result": null,
  "error": {
    "type": "https://openvibe.network/problems/tools.job.timeout",
    "title": "Gateway Timeout",
    "status": 504,
    "code": "tools.job.timeout",
    "detail": "The job ran longer than 900 s",
    "error": "The job ran longer than 900 s"
  },
  "retryable": true,
  "retry_of": null,
  "retried_by": null,
  "references": [],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/retry",
    "retried_by": null
  }
}
Valid: queued
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "queued",
  "progress": {
    "percent": null,
    "message": null
  },
  "attempts": 0,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": null,
  "finished_at": null,
  "expires_at": "2026-09-23T10:00:00.000Z",
  "result": null,
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "retry": null,
    "retried_by": null
  }
}
Valid: succeeded-local-after-media-error
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "succeeded",
  "progress": {
    "percent": 100,
    "message": "Done"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:00:04.000Z",
  "expires_at": "2026-09-24T09:00:04.000Z",
  "result": {
    "files": [
      {
        "name": "photo.png",
        "mime": "image/png",
        "size": 90112,
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "storage": "local",
        "media": null,
        "media_error": "Could not store the result in OpenVibe.Media; it is kept on this server until the job expires",
        "url": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/files/0"
      }
    ],
    "data": {
      "width": 1024,
      "height": 768
    }
  },
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": null,
    "retried_by": null
  }
}
Valid: succeeded-media-referenced
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "succeeded",
  "progress": {
    "percent": 100,
    "message": "Done"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:00:04.000Z",
  "expires_at": null,
  "result": {
    "files": [
      {
        "name": "photo.png",
        "mime": "image/png",
        "size": 90112,
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "storage": "media",
        "media": {
          "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
          "role": "output",
          "namespace": "tools",
          "size_bytes": 90112,
          "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "mime_type": "image/png",
          "status": "ready"
        },
        "url": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/files/0"
      }
    ],
    "data": {
      "width": 1024,
      "height": 768
    }
  },
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [
    {
      "ref": "community:paste:p_123",
      "created_at": "2026-09-23T09:05:00.000Z"
    }
  ],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": null,
    "retried_by": null
  },
  "tool": "png"
}
Rejected: file-without-url
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "succeeded",
  "progress": {
    "percent": 100,
    "message": "Done"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:00:04.000Z",
  "expires_at": null,
  "result": {
    "files": [
      {
        "name": "photo.png",
        "mime": "image/png",
        "size": 90112,
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "storage": "media",
        "media": {
          "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
          "role": "output",
          "namespace": "tools",
          "size_bytes": 90112,
          "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "mime_type": "image/png",
          "status": "ready"
        }
      }
    ],
    "data": {
      "width": 1024,
      "height": 768
    }
  },
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [
    {
      "ref": "community:paste:p_123",
      "created_at": "2026-09-23T09:05:00.000Z"
    }
  ],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": null,
    "retried_by": null
  },
  "tool": "png"
}
Rejected: legacy-media-id
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "succeeded",
  "progress": {
    "percent": 100,
    "message": "Done"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:00:04.000Z",
  "expires_at": null,
  "result": {
    "files": [
      {
        "name": "photo.png",
        "mime": "image/png",
        "size": 90112,
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "storage": "media",
        "media": {
          "media_id": "legacy:live:vod:42",
          "role": "output",
          "namespace": "tools",
          "size_bytes": 90112,
          "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "mime_type": "image/png",
          "status": "ready"
        },
        "url": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/files/0"
      }
    ],
    "data": {
      "width": 1024,
      "height": 768
    }
  },
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [
    {
      "ref": "community:paste:p_123",
      "created_at": "2026-09-23T09:05:00.000Z"
    }
  ],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": null,
    "retried_by": null
  },
  "tool": "png"
}
Rejected: no-links
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "queued",
  "progress": {
    "percent": null,
    "message": null
  },
  "attempts": 0,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": null,
  "finished_at": null,
  "expires_at": "2026-09-23T10:00:00.000Z",
  "result": null,
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": []
}
Rejected: server-path-in-file
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "succeeded",
  "progress": {
    "percent": 100,
    "message": "Done"
  },
  "attempts": 1,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": "2026-09-23T09:00:01.000Z",
  "finished_at": "2026-09-23T09:00:04.000Z",
  "expires_at": null,
  "result": {
    "files": [
      {
        "name": "photo.png",
        "mime": "image/png",
        "size": 90112,
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "storage": "media",
        "media": {
          "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
          "role": "output",
          "namespace": "tools",
          "size_bytes": 90112,
          "content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
          "mime_type": "image/png",
          "status": "ready"
        },
        "url": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/files/0",
        "path": "/opt/openvibe.tools/data/jobs/x/out/0-photo.png"
      }
    ],
    "data": {
      "width": 1024,
      "height": 768
    }
  },
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [
    {
      "ref": "community:paste:p_123",
      "created_at": "2026-09-23T09:05:00.000Z"
    }
  ],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": null,
    "retry": null,
    "retried_by": null
  },
  "tool": "png"
}
Rejected: unknown-state
{
  "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object": "tools.job",
  "service": "img",
  "type": "img.process",
  "type_version": 1,
  "state": "paused",
  "progress": {
    "percent": null,
    "message": null
  },
  "attempts": 0,
  "max_attempts": 3,
  "cancel_requested": false,
  "created_at": "2026-09-23T09:00:00.000Z",
  "started_at": null,
  "finished_at": null,
  "expires_at": "2026-09-23T10:00:00.000Z",
  "result": null,
  "error": null,
  "retryable": false,
  "retry_of": null,
  "retried_by": null,
  "references": [],
  "links": {
    "self": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "events": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/events",
    "cancel": "/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "retry": null,
    "retried_by": null
  }
}

Validate

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