{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://quran-tadabur-api.pages.dev/v1/schema.json",
  "title": "Tadabbur video API (v1)",
  "description": "The documents of the static API generated by `tadabur api build` from the published catalog. Every document is a JSON object; `$defs` describes each kind. Times are integer milliseconds. URLs that start with `/` are relative to the API origin; media URLs are absolute.",
  "oneOf": [
    { "$ref": "#/$defs/indexDoc" },
    { "$ref": "#/$defs/surahsDoc" },
    { "$ref": "#/$defs/surahDoc" },
    { "$ref": "#/$defs/pageDoc" },
    { "$ref": "#/$defs/ayahDoc" },
    { "$ref": "#/$defs/buildDoc" },
    { "$ref": "#/$defs/errorDoc" }
  ],
  "$defs": {
    "apiVersion": { "const": 1 },
    "count": { "type": "integer", "minimum": 0 },
    "surahNumber": { "type": "integer", "minimum": 1, "maximum": 114 },
    "ayahNumber": { "type": "integer", "minimum": 1, "maximum": 286 },
    "pageNumber": { "type": "integer", "minimum": 1, "maximum": 604 },
    "juzNumber": { "type": "integer", "minimum": 1, "maximum": 30 },
    "verseKey": { "type": "string", "pattern": "^\\d{1,3}:\\d{1,3}$" },
    "videoId": { "type": "string", "pattern": "^[A-Za-z0-9_-]{11}-(\\d{1,3}-\\d{1,3}-\\d{1,3}(-p\\d+)?|intro|episode)$" },
    "youtubeId": { "type": "string", "pattern": "^[A-Za-z0-9_-]{11}$" },
    "path": { "type": "string", "pattern": "^/v1/[a-z0-9/._-]+\\.json$" },
    "nullablePath": { "oneOf": [{ "$ref": "#/$defs/path" }, { "type": "null" }] },
    "httpsUrl": { "type": "string", "pattern": "^https://" },
    "coverage": {
      "description": "Ayahs by the best video they have. Intros do not count: they introduce an episode, they are not the tadabbur of an ayah.",
      "type": "object",
      "additionalProperties": false,
      "required": ["ayahs", "hosted", "clip", "episode", "none"],
      "properties": {
        "ayahs": { "$ref": "#/$defs/count" },
        "hosted": { "$ref": "#/$defs/count", "description": "an own clip with a hosted MP4" },
        "clip": { "$ref": "#/$defs/count", "description": "an own clip that is played from YouTube (not cut yet)" },
        "episode": { "$ref": "#/$defs/count", "description": "only the whole episode" },
        "none": { "$ref": "#/$defs/count" }
      }
    },
    "series": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "title", "channel", "url", "speaker", "attribution"],
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "title": { "type": "string" },
        "channel": { "type": "string" },
        "url": { "$ref": "#/$defs/httpsUrl" },
        "speaker": { "type": "string" },
        "attribution": { "type": "string" }
      }
    },
    "seriesList": { "type": "array", "items": { "$ref": "#/$defs/series" } },
    "surahInfo": {
      "type": "object",
      "additionalProperties": false,
      "required": ["number", "nameArabic", "nameSimple", "translatedName", "revelationPlace", "versesCount", "pages", "url"],
      "properties": {
        "number": { "$ref": "#/$defs/surahNumber" },
        "nameArabic": { "type": "string" },
        "nameSimple": { "type": "string" },
        "translatedName": { "type": "string" },
        "revelationPlace": { "enum": ["makkah", "madinah"] },
        "versesCount": { "$ref": "#/$defs/ayahNumber" },
        "pages": {
          "type": "array",
          "prefixItems": [{ "$ref": "#/$defs/pageNumber" }, { "$ref": "#/$defs/pageNumber" }],
          "items": false,
          "minItems": 2
        },
        "url": { "$ref": "#/$defs/path" }
      }
    },
    "video": {
      "description": "One tadabbur video. `kind`: `clip` = cut for these ayahs; `episode` = the whole episode, for ayahs whose own clip is not aligned yet; `intro` = the part of an episode before its first ayah.",
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "kind", "surah", "ayahFrom", "ayahTo", "durationMs", "video", "poster", "youtube", "episode", "series", "review"],
      "properties": {
        "id": { "$ref": "#/$defs/videoId" },
        "kind": { "enum": ["clip", "episode", "intro"] },
        "surah": { "$ref": "#/$defs/surahNumber" },
        "ayahFrom": { "$ref": "#/$defs/ayahNumber" },
        "ayahTo": { "$ref": "#/$defs/ayahNumber" },
        "part": {
          "type": "object",
          "additionalProperties": false,
          "required": ["index", "count"],
          "properties": {
            "index": { "type": "integer", "minimum": 1 },
            "count": { "type": "integer", "minimum": 2 }
          }
        },
        "durationMs": { "type": "integer", "minimum": 1 },
        "video": {
          "description": "The hosted MP4 (portrait 540x960), or null when the segment plays from YouTube only.",
          "oneOf": [
            {
              "type": "object",
              "additionalProperties": false,
              "required": ["url", "type", "width", "height"],
              "properties": {
                "url": { "type": "string", "pattern": "^https://.+\\.mp4$" },
                "type": { "const": "video/mp4" },
                "width": { "type": "integer", "minimum": 1 },
                "height": { "type": "integer", "minimum": 1 }
              }
            },
            { "type": "null" }
          ]
        },
        "poster": {
          "type": "object",
          "additionalProperties": false,
          "required": ["url", "type"],
          "properties": {
            "url": { "type": "string", "pattern": "^https://.+\\.jpg$" },
            "type": { "const": "image/jpeg" }
          }
        },
        "youtube": {
          "description": "The same segment in the source video. `startMs`/`endMs` are source-video time; `embedUrl` is bounded by `start` and `end` (whole seconds).",
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "startMs", "endMs", "url", "embedUrl"],
          "properties": {
            "id": { "$ref": "#/$defs/youtubeId" },
            "startMs": { "type": "integer", "minimum": 0 },
            "endMs": { "type": "integer", "minimum": 1 },
            "url": { "type": "string", "pattern": "^https://www\\.youtube\\.com/watch\\?v=[A-Za-z0-9_-]{11}&t=\\d+s$" },
            "embedUrl": { "type": "string", "pattern": "^https://www\\.youtube\\.com/embed/[A-Za-z0-9_-]{11}\\?start=\\d+&end=\\d+$" }
          }
        },
        "episode": {
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "title", "position", "url"],
          "properties": {
            "id": { "$ref": "#/$defs/youtubeId" },
            "title": { "type": "string" },
            "position": { "type": "integer", "minimum": 1 },
            "url": { "type": "string", "pattern": "^https://www\\.youtube\\.com/watch\\?v=[A-Za-z0-9_-]{11}$" }
          }
        },
        "series": { "type": "string", "minLength": 1 },
        "cues": {
          "description": "Where the discussion moves on to a later ayah of the range. `offsetMs` is relative to the start of the video (the hosted MP4); add `youtube.startMs` for the source video.",
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["ayah", "offsetMs"],
            "properties": {
              "ayah": { "$ref": "#/$defs/ayahNumber" },
              "offsetMs": { "type": "integer", "minimum": 0 }
            }
          }
        },
        "review": {
          "description": "How the boundaries were made: `decision` auto = published without a manual review.",
          "type": "object",
          "additionalProperties": false,
          "required": ["decision", "confidence", "engine"],
          "properties": {
            "decision": { "enum": ["auto", "approved"] },
            "confidence": { "enum": ["high", "medium", "low", "n/a"] },
            "engine": { "enum": ["rules", "gemini", "auto", "title", "manual"] },
            "method": { "enum": ["transcript", "playback"] },
            "reviewer": { "type": "string", "minLength": 1 }
          }
        }
      }
    },
    "videoList": { "type": "array", "items": { "$ref": "#/$defs/video" } },
    "ayahVideos": {
      "description": "Each ayah with the ids of its videos, best first (see `ranking` in /v1/index.json).",
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["key", "surah", "ayah", "page", "videos"],
        "properties": {
          "key": { "$ref": "#/$defs/verseKey" },
          "surah": { "$ref": "#/$defs/surahNumber" },
          "ayah": { "$ref": "#/$defs/ayahNumber" },
          "page": { "$ref": "#/$defs/pageNumber" },
          "videos": { "type": "array", "items": { "$ref": "#/$defs/videoId" } }
        }
      }
    },
    "indexDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "name", "description", "endpoints", "ranking", "media", "counts", "coverage", "series", "attribution", "caching"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "endpoints": {
          "type": "object",
          "additionalProperties": false,
          "required": ["index", "surahs", "surah", "page", "ayah", "schema", "build"],
          "properties": {
            "index": { "type": "string" },
            "surahs": { "type": "string" },
            "surah": { "type": "string" },
            "page": { "type": "string" },
            "ayah": { "type": "string" },
            "schema": { "type": "string" },
            "build": { "type": "string" }
          }
        },
        "ranking": { "type": "string" },
        "media": {
          "type": "object",
          "additionalProperties": false,
          "required": ["baseUrl", "note"],
          "properties": {
            "baseUrl": { "$ref": "#/$defs/httpsUrl" },
            "note": { "type": "string" }
          }
        },
        "counts": {
          "type": "object",
          "additionalProperties": false,
          "required": ["videos", "hostedVideos", "byKind", "episodes", "surahs", "pages", "ayahs"],
          "properties": {
            "videos": { "$ref": "#/$defs/count" },
            "hostedVideos": { "$ref": "#/$defs/count" },
            "byKind": {
              "type": "object",
              "additionalProperties": false,
              "required": ["clip", "episode", "intro"],
              "properties": {
                "clip": { "$ref": "#/$defs/count" },
                "episode": { "$ref": "#/$defs/count" },
                "intro": { "$ref": "#/$defs/count" }
              }
            },
            "episodes": { "$ref": "#/$defs/count" },
            "surahs": { "const": 114 },
            "pages": { "const": 604 },
            "ayahs": { "const": 6236 }
          }
        },
        "coverage": { "$ref": "#/$defs/coverage" },
        "series": { "$ref": "#/$defs/seriesList" },
        "attribution": {
          "type": "object",
          "additionalProperties": false,
          "required": ["videos", "quran"],
          "properties": {
            "videos": { "type": "string" },
            "quran": { "type": "string" }
          }
        },
        "caching": { "type": "string" }
      }
    },
    "surahsDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "surahs"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "surahs": {
          "type": "array",
          "minItems": 114,
          "maxItems": 114,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["surah", "videos", "coverage"],
            "properties": {
              "surah": { "$ref": "#/$defs/surahInfo" },
              "videos": { "$ref": "#/$defs/count" },
              "coverage": { "$ref": "#/$defs/coverage" }
            }
          }
        }
      }
    },
    "surahDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "surah", "coverage", "videos", "ayahs", "series", "links"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "surah": { "$ref": "#/$defs/surahInfo" },
        "coverage": { "$ref": "#/$defs/coverage" },
        "videos": { "$ref": "#/$defs/videoList" },
        "ayahs": { "$ref": "#/$defs/ayahVideos" },
        "series": { "$ref": "#/$defs/seriesList" },
        "links": {
          "type": "object",
          "additionalProperties": false,
          "required": ["self", "prev", "next", "index"],
          "properties": {
            "self": { "$ref": "#/$defs/path" },
            "prev": { "$ref": "#/$defs/nullablePath" },
            "next": { "$ref": "#/$defs/nullablePath" },
            "index": { "$ref": "#/$defs/path" }
          }
        }
      }
    },
    "pageDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "page", "videos", "ayahs", "series", "links"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "page": {
          "type": "object",
          "additionalProperties": false,
          "required": ["number", "juz", "ranges", "url"],
          "properties": {
            "number": { "$ref": "#/$defs/pageNumber" },
            "juz": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/juzNumber" } },
            "ranges": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["surah", "from", "to"],
                "properties": {
                  "surah": { "$ref": "#/$defs/surahNumber" },
                  "from": { "$ref": "#/$defs/ayahNumber" },
                  "to": { "$ref": "#/$defs/ayahNumber" }
                }
              }
            },
            "url": { "$ref": "#/$defs/path" }
          }
        },
        "videos": { "$ref": "#/$defs/videoList" },
        "ayahs": { "$ref": "#/$defs/ayahVideos" },
        "series": { "$ref": "#/$defs/seriesList" },
        "links": {
          "type": "object",
          "additionalProperties": false,
          "required": ["self", "prev", "next", "surahs"],
          "properties": {
            "self": { "$ref": "#/$defs/path" },
            "prev": { "$ref": "#/$defs/nullablePath" },
            "next": { "$ref": "#/$defs/nullablePath" },
            "surahs": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/path" } }
          }
        }
      }
    },
    "ayahDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "ayah", "surah", "videos", "series", "links"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "ayah": {
          "type": "object",
          "additionalProperties": false,
          "required": ["key", "surah", "ayah", "page", "juz"],
          "properties": {
            "key": { "$ref": "#/$defs/verseKey" },
            "surah": { "$ref": "#/$defs/surahNumber" },
            "ayah": { "$ref": "#/$defs/ayahNumber" },
            "page": { "$ref": "#/$defs/pageNumber" },
            "juz": { "$ref": "#/$defs/juzNumber" }
          }
        },
        "surah": {
          "type": "object",
          "additionalProperties": false,
          "required": ["number", "nameArabic", "nameSimple", "url"],
          "properties": {
            "number": { "$ref": "#/$defs/surahNumber" },
            "nameArabic": { "type": "string" },
            "nameSimple": { "type": "string" },
            "url": { "$ref": "#/$defs/path" }
          }
        },
        "videos": { "$ref": "#/$defs/videoList" },
        "series": { "$ref": "#/$defs/seriesList" },
        "links": {
          "type": "object",
          "additionalProperties": false,
          "required": ["self", "prev", "next", "page", "surah"],
          "properties": {
            "self": { "$ref": "#/$defs/path" },
            "prev": { "$ref": "#/$defs/nullablePath" },
            "next": { "$ref": "#/$defs/nullablePath" },
            "page": { "$ref": "#/$defs/path" },
            "surah": { "$ref": "#/$defs/path" }
          }
        }
      }
    },
    "buildDoc": {
      "description": "The identity of this deployment: the SHA-256 of every other generated file (path and content), so it changes with any data, media base, header or documentation change and with nothing else.",
      "type": "object",
      "additionalProperties": false,
      "required": ["apiVersion", "generator", "siteSha256", "files", "bytes"],
      "properties": {
        "apiVersion": { "$ref": "#/$defs/apiVersion" },
        "generator": { "type": "string" },
        "siteSha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
        "files": { "$ref": "#/$defs/count" },
        "bytes": { "$ref": "#/$defs/count" }
      }
    },
    "errorDoc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["error"],
      "properties": {
        "error": {
          "type": "object",
          "additionalProperties": false,
          "required": ["status", "code", "message"],
          "properties": {
            "status": { "const": 404 },
            "code": { "const": "not_found" },
            "message": { "type": "string" }
          }
        }
      }
    }
  }
}
