{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/scribiz/json-schema/scribiz-job-schema.json",
  "title": "Job",
  "x-generated": "2026-11-08",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/scribiz-openapi.json#/components/schemas/Job",
  "properties": {
    "attempt": {
      "type": "integer"
    },
    "cache_hit": {
      "type": [
        "boolean",
        "null"
      ]
    },
    "cancel_requested": {
      "type": "boolean"
    },
    "created_at": {
      "description": "Epoch milliseconds.",
      "type": "integer"
    },
    "degraded": {
      "type": "object"
    },
    "error": {
      "oneOf": [
        {
          "$ref": "#/$defs/ErrorInfo"
        },
        {
          "type": "null"
        }
      ]
    },
    "estimate": {
      "oneOf": [
        {
          "$ref": "#/$defs/Quote"
        },
        {
          "type": "null"
        }
      ]
    },
    "events_url": {
      "type": "string"
    },
    "expires_at": {
      "type": "integer"
    },
    "finished_at": {
      "type": [
        "integer",
        "null"
      ]
    },
    "input": {
      "type": "object"
    },
    "job_id": {
      "type": "string"
    },
    "minutes_charged": {
      "type": [
        "number",
        "null"
      ]
    },
    "mode": {
      "type": "string"
    },
    "progress": {
      "description": "0 to 1, never going backwards; null until known.",
      "type": [
        "number",
        "null"
      ]
    },
    "result": {
      "oneOf": [
        {
          "$ref": "#/$defs/Context"
        },
        {
          "type": "null"
        }
      ]
    },
    "result_url": {
      "type": "string"
    },
    "stage": {
      "type": [
        "string",
        "null"
      ]
    },
    "started_at": {
      "type": [
        "integer",
        "null"
      ]
    },
    "status": {
      "enum": [
        "queued",
        "running",
        "succeeded",
        "failed",
        "canceled"
      ]
    }
  },
  "required": [
    "job_id",
    "status",
    "mode",
    "estimate",
    "result",
    "error",
    "events_url",
    "result_url",
    "created_at",
    "expires_at"
  ],
  "type": "object",
  "$defs": {
    "Context": {
      "description": "The result of a video: transcript, what was on screen, summary and chapters. Times are seconds. The same JSON the CLI writes with --format json.",
      "properties": {
        "coverage": {
          "type": "object"
        },
        "id": {
          "description": "youtube:<id>, instagram:<id>, ... or local:<key> for an upload.",
          "type": "string"
        },
        "meta": {
          "type": "object"
        },
        "produced": {
          "type": "object"
        },
        "schema": {
          "const": 1
        },
        "source": {
          "type": "object"
        },
        "synthesis": {
          "type": [
            "object",
            "null"
          ]
        },
        "tiers": {
          "items": {
            "type": "object"
          },
          "type": "array"
        },
        "transcript": {
          "type": [
            "object",
            "null"
          ]
        },
        "usage": {
          "type": "object"
        },
        "visual": {
          "type": [
            "object",
            "null"
          ]
        },
        "warnings": {
          "items": {
            "type": "object"
          },
          "type": "array"
        }
      },
      "required": [
        "schema",
        "id",
        "meta",
        "source",
        "transcript",
        "visual",
        "synthesis",
        "tiers",
        "usage",
        "warnings"
      ],
      "type": "object"
    },
    "ErrorInfo": {
      "description": "The engine's error: what a failed job carries and what the SSE `error` event holds.",
      "properties": {
        "code": {
          "type": "string"
        },
        "hint": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "retryAfterMs": {
          "type": "number"
        },
        "retryable": {
          "type": "boolean"
        },
        "scope": {
          "enum": [
            "job",
            "queue",
            "account"
          ]
        },
        "stage": {
          "type": "string"
        },
        "status": {
          "type": "number"
        }
      },
      "required": [
        "code",
        "message",
        "retryable",
        "scope",
        "stage"
      ],
      "type": "object"
    },
    "Quote": {
      "properties": {
        "cached": {
          "description": "Everything is already stored: nothing is charged.",
          "type": "boolean"
        },
        "caption_credits": {
          "description": "Maximum caption credits for a strict YouTube request. Media minutes are zero.",
          "minimum": 0,
          "type": "integer"
        },
        "charges_as": {
          "enum": [
            "caption-item",
            "captions",
            "audio",
            "visual",
            "full"
          ]
        },
        "length_known": {
          "const": false,
          "description": "Present (false) when the length of the source could not be read before the run (a YouTube link that yt-dlp and the YouTube page both refused; a direct media link not yet downloaded). The job is charged from the length it measures."
        },
        "max_media_seconds": {
          "description": "Only with `length_known: false` on a YouTube link: the longest stretch the job reads (a clip that ends there): never more than the plan allows per video and never more than the minutes left. `minutes` is the most the job can then use. When the server can get the video's own captions they answer instead, also for a longer video, and the job costs less.",
          "type": "number"
        },
        "media_seconds": {
          "description": "Seconds of media the quote is for; null when the length is not known (`length_known: false`).",
          "type": [
            "number",
            "null"
          ]
        },
        "minutes": {
          "description": "Minutes this job uses when it runs as quoted (0 when cached), including the half minute for each media minute when the full proofread is part of it (`proofread: \"full\"` or a `vocabulary`). It is charged only if the pass runs. With `length_known: false`: the most it can use.",
          "type": "number"
        },
        "tiers": {
          "items": {
            "enum": [
              "captions",
              "audio",
              "visual",
              "synthesis"
            ]
          },
          "type": "array"
        }
      },
      "required": [
        "minutes",
        "media_seconds",
        "tiers",
        "cached",
        "charges_as"
      ],
      "type": "object"
    }
  }
}