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
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
id | string | yes |
| |
object | const | yes |
| |
service | string | yes | The Tools satellite that runs the job (img, audio, docs…). |
|
tool | string | The 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. |
| |
type | string | yes | Job type, e.g. img.process. |
|
type_version | integer | yes |
| |
state | enum | yes | Terminal: succeeded, failed, cancelled. |
|
progress | object | yes |
| |
progress.percent | number | null | yes |
| |
progress.message | string | null | yes |
| |
attempts | integer | yes |
| |
max_attempts | integer | yes |
| |
cancel_requested | boolean | yes | DELETE asked a running job to stop; it ends cancelled. | |
created_at | string | yes |
| |
started_at | string | null | yes |
| |
finished_at | string | null | yes |
| |
expires_at | string | null | yes | When the job, its events and its result files are pruned; null while a reference keeps the result. |
|
result | object | null | yes | ||
error | one of | yes | ||
retryable | boolean | yes | Retrying may succeed (a timeout, a restart); POST links.retry makes a new job. | |
retry_of | string | null | yes | The failed job this one retries. |
|
retried_by | string | null | yes | The job that retried this failed one; asking again returns it. |
|
references | array of object | yes | What keeps the result (PUT /api/v1/jobs/:id/references/:ref), e.g. community:paste:p_123. |
|
references[].ref | string | yes |
| |
references[].created_at | string | yes |
| |
links | object | yes |
| |
links.self | string | yes |
| |
links.events | string | yes |
| |
links.cancel | string | null | yes | DELETE it while the job is queued or running. |
|
links.retry | string | null | yes | POST it when the job failed. |
|
links.retried_by | string | null | yes |
|
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 }] }