{
  "$defs": {
    "byteSize": {
      "description": "Size in bytes: an integer, or a string with a binary suffix B, KiB, MiB, GiB or TiB (decimal MB is rejected).",
      "examples": [
        "10MiB",
        "512KiB",
        1048576
      ],
      "oneOf": [
        {
          "minimum": 0,
          "type": "integer"
        },
        {
          "pattern": "^\\s*[0-9]+ *([KkMmGgTt][Ii][Bb]|[Bb])?\\s*$",
          "type": "string"
        }
      ]
    },
    "cidr": {
      "description": "A network in CIDR notation or a single address; host bits must be zero.",
      "examples": [
        "10.0.0.0/8",
        "fd00::1"
      ],
      "type": "string"
    },
    "duration": {
      "description": "Go duration (5s, 1m30s, 24h); negative values are rejected.",
      "examples": [
        "5s",
        "1m30s",
        "24h"
      ],
      "pattern": "^\\s*\\+?(0|(([0-9]+(\\.[0-9]*)?|\\.[0-9]+)(ns|us|µs|μs|ms|s|m|h))+)\\s*$",
      "type": "string"
    },
    "fileRef": {
      "additionalProperties": false,
      "description": "Reads the value from a file (like XI_…_FILE): at most 64 KiB, trailing newline trimmed.",
      "properties": {
        "file": {
          "description": "Absolute path of the file holding the value.",
          "pattern": "^/",
          "type": "string"
        }
      },
      "required": [
        "file"
      ],
      "type": "object"
    },
    "hexKey": {
      "description": "Hex-encoded key material, or {file: /absolute/path} holding it. Printed as [REDACTED].",
      "oneOf": [
        {
          "pattern": "^\\s*([0-9a-fA-F]{2})+\\s*$",
          "type": "string"
        },
        {
          "$ref": "#/$defs/fileRef"
        }
      ],
      "writeOnly": true
    },
    "intList": {
      "description": "Integers: a string of comma-separated items, each a number, a range a-b or a stepped range a-b/step (5-100/5), or a list of such items.",
      "examples": [
        "640-1080/220",
        [
          16,
          32,
          64
        ]
      ],
      "oneOf": [
        {
          "pattern": "^\\s*[0-9]+\\s*(-\\s*[0-9]+\\s*(/\\s*[0-9]+\\s*)?)?(,\\s*[0-9]+\\s*(-\\s*[0-9]+\\s*(/\\s*[0-9]+\\s*)?)?)*$",
          "type": "string"
        },
        {
          "items": {
            "oneOf": [
              {
                "minimum": 0,
                "type": "integer"
              },
              {
                "pattern": "^\\s*[0-9]+\\s*(-\\s*[0-9]+\\s*(/\\s*[0-9]+\\s*)?)?$",
                "type": "string"
              }
            ]
          },
          "type": "array"
        }
      ]
    },
    "mount": {
      "additionalProperties": false,
      "description": "An extra mount of the dialect (file only; no environment variable).",
      "properties": {
        "hosts": {
          "description": "Host bindings",
          "items": {
            "type": "string"
          },
          "markdownDescription": "Host bindings",
          "type": "array"
        },
        "keys": {
          "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)",
          "items": {
            "$ref": "#/$defs/secret"
          },
          "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)",
          "type": "array"
        },
        "prefix": {
          "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md",
          "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md",
          "type": "string"
        },
        "source": {
          "description": "Bound source for relative paths",
          "markdownDescription": "Bound source for relative paths",
          "type": "string"
        },
        "unsigned_policy": {
          "description": "Per-dialect override\n\nDefault: global",
          "enum": [
            "allow",
            "restricted",
            "deny"
          ],
          "markdownDescription": "Per-dialect override\n\nDefault: global",
          "type": "string"
        }
      },
      "type": "object"
    },
    "secret": {
      "description": "A secret: a literal string or {file: /absolute/path}. Printed as [REDACTED].",
      "oneOf": [
        {
          "type": "string"
        },
        {
          "$ref": "#/$defs/fileRef"
        }
      ],
      "writeOnly": true
    },
    "source": {
      "description": "A named source; the fields that apply depend on type.",
      "oneOf": [
        {
          "$ref": "#/$defs/source_http"
        },
        {
          "$ref": "#/$defs/source_s3"
        },
        {
          "$ref": "#/$defs/source_gcs"
        },
        {
          "$ref": "#/$defs/source_azure"
        },
        {
          "$ref": "#/$defs/source_fs"
        },
        {
          "$ref": "#/$defs/source_ftp"
        },
        {
          "$ref": "#/$defs/source_sftp"
        }
      ],
      "required": [
        "type"
      ],
      "type": "object"
    },
    "source_azure": {
      "additionalProperties": false,
      "properties": {
        "account": {
          "description": "azure; one of _ACCOUNT, _ENDPOINT or _CONNECTION_STRING is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs AccountName+AccountKey or SharedAccessSignature (no UseDevelopmentStorage=true: give Azurite's explicit form); _ACCOUNT_KEY (base64) and _SAS_TOKEN (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; _ACCOUNT is 3-24 lowercase letters and digits and containers (_BUCKETS) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; $root, $web); _ENDPOINT overrides https://<account>.blob.core.windows.net/ (Azurite: http://127.0.0.1:10000/devstoreaccount1; no query with a SAS token)\n\nEnv: XI_SOURCES_<NAME>_ACCOUNT",
          "markdownDescription": "azure; one of `_ACCOUNT`, `_ENDPOINT` or `_CONNECTION_STRING` is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs `AccountName`+`AccountKey` or `SharedAccessSignature` (no `UseDevelopmentStorage=true`: give Azurite's explicit form); `_ACCOUNT_KEY` (base64) and `_SAS_TOKEN` (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; `_ACCOUNT` is 3-24 lowercase letters and digits and containers (`_BUCKETS`) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; `$root`, `$web`); `_ENDPOINT` overrides `https://<account>.blob.core.windows.net/` (Azurite: `http://127.0.0.1:10000/devstoreaccount1`; no query with a SAS token)\n\nEnv: `XI_SOURCES_<NAME>_ACCOUNT`",
          "title": "XI_SOURCES_<NAME>_ACCOUNT",
          "type": "string"
        },
        "account_key": {
          "$ref": "#/$defs/secret",
          "description": "azure; one of _ACCOUNT, _ENDPOINT or _CONNECTION_STRING is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs AccountName+AccountKey or SharedAccessSignature (no UseDevelopmentStorage=true: give Azurite's explicit form); _ACCOUNT_KEY (base64) and _SAS_TOKEN (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; _ACCOUNT is 3-24 lowercase letters and digits and containers (_BUCKETS) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; $root, $web); _ENDPOINT overrides https://<account>.blob.core.windows.net/ (Azurite: http://127.0.0.1:10000/devstoreaccount1; no query with a SAS token)\n\nEnv: XI_SOURCES_<NAME>_ACCOUNT_KEY (or XI_SOURCES_<NAME>_ACCOUNT_KEY_FILE)",
          "markdownDescription": "azure; one of `_ACCOUNT`, `_ENDPOINT` or `_CONNECTION_STRING` is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs `AccountName`+`AccountKey` or `SharedAccessSignature` (no `UseDevelopmentStorage=true`: give Azurite's explicit form); `_ACCOUNT_KEY` (base64) and `_SAS_TOKEN` (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; `_ACCOUNT` is 3-24 lowercase letters and digits and containers (`_BUCKETS`) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; `$root`, `$web`); `_ENDPOINT` overrides `https://<account>.blob.core.windows.net/` (Azurite: `http://127.0.0.1:10000/devstoreaccount1`; no query with a SAS token)\n\nEnv: `XI_SOURCES_<NAME>_ACCOUNT_KEY` (or `XI_SOURCES_<NAME>_ACCOUNT_KEY_FILE`)",
          "title": "XI_SOURCES_<NAME>_ACCOUNT_KEY"
        },
        "bucket": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKET",
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKET`",
          "title": "XI_SOURCES_<NAME>_BUCKET",
          "type": "string"
        },
        "buckets": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKETS",
          "items": {
            "type": "string"
          },
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKETS`",
          "title": "XI_SOURCES_<NAME>_BUCKETS",
          "type": "array"
        },
        "connection_string": {
          "$ref": "#/$defs/secret",
          "description": "azure; one of _ACCOUNT, _ENDPOINT or _CONNECTION_STRING is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs AccountName+AccountKey or SharedAccessSignature (no UseDevelopmentStorage=true: give Azurite's explicit form); _ACCOUNT_KEY (base64) and _SAS_TOKEN (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; _ACCOUNT is 3-24 lowercase letters and digits and containers (_BUCKETS) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; $root, $web); _ENDPOINT overrides https://<account>.blob.core.windows.net/ (Azurite: http://127.0.0.1:10000/devstoreaccount1; no query with a SAS token)\n\nEnv: XI_SOURCES_<NAME>_CONNECTION_STRING (or XI_SOURCES_<NAME>_CONNECTION_STRING_FILE)",
          "markdownDescription": "azure; one of `_ACCOUNT`, `_ENDPOINT` or `_CONNECTION_STRING` is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs `AccountName`+`AccountKey` or `SharedAccessSignature` (no `UseDevelopmentStorage=true`: give Azurite's explicit form); `_ACCOUNT_KEY` (base64) and `_SAS_TOKEN` (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; `_ACCOUNT` is 3-24 lowercase letters and digits and containers (`_BUCKETS`) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; `$root`, `$web`); `_ENDPOINT` overrides `https://<account>.blob.core.windows.net/` (Azurite: `http://127.0.0.1:10000/devstoreaccount1`; no query with a SAS token)\n\nEnv: `XI_SOURCES_<NAME>_CONNECTION_STRING` (or `XI_SOURCES_<NAME>_CONNECTION_STRING_FILE`)",
          "title": "XI_SOURCES_<NAME>_CONNECTION_STRING"
        },
        "endpoint": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_ENDPOINT",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_ENDPOINT`",
          "title": "XI_SOURCES_<NAME>_ENDPOINT",
          "type": "string"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "sas_token": {
          "$ref": "#/$defs/secret",
          "description": "azure; one of _ACCOUNT, _ENDPOINT or _CONNECTION_STRING is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs AccountName+AccountKey or SharedAccessSignature (no UseDevelopmentStorage=true: give Azurite's explicit form); _ACCOUNT_KEY (base64) and _SAS_TOKEN (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; _ACCOUNT is 3-24 lowercase letters and digits and containers (_BUCKETS) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; $root, $web); _ENDPOINT overrides https://<account>.blob.core.windows.net/ (Azurite: http://127.0.0.1:10000/devstoreaccount1; no query with a SAS token)\n\nEnv: XI_SOURCES_<NAME>_SAS_TOKEN (or XI_SOURCES_<NAME>_SAS_TOKEN_FILE)",
          "markdownDescription": "azure; one of `_ACCOUNT`, `_ENDPOINT` or `_CONNECTION_STRING` is required; the connection string stands alone (Validate refuses it next to account, key, SAS or endpoint) and needs `AccountName`+`AccountKey` or `SharedAccessSignature` (no `UseDevelopmentStorage=true`: give Azurite's explicit form); `_ACCOUNT_KEY` (base64) and `_SAS_TOKEN` (the token, not a SAS URL) are exclusive, else DefaultAzureCredential; `_ACCOUNT` is 3-24 lowercase letters and digits and containers (`_BUCKETS`) follow Azure's naming rule (3-63 lowercase letters, digits, single hyphens; `$root`, `$web`); `_ENDPOINT` overrides `https://<account>.blob.core.windows.net/` (Azurite: `http://127.0.0.1:10000/devstoreaccount1`; no query with a SAS token)\n\nEnv: `XI_SOURCES_<NAME>_SAS_TOKEN` (or `XI_SOURCES_<NAME>_SAS_TOKEN_FILE`)",
          "title": "XI_SOURCES_<NAME>_SAS_TOKEN"
        },
        "type": {
          "const": "azure",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        }
      },
      "required": [
        "type"
      ],
      "title": "azure source",
      "type": "object"
    },
    "source_fs": {
      "additionalProperties": false,
      "properties": {
        "follow_symlinks": {
          "description": "fs: follow symlinks whose target stays inside the root (links leaving it are always refused)\n\nEnv: XI_SOURCES_<NAME>_FOLLOW_SYMLINKS",
          "markdownDescription": "fs: follow symlinks whose target stays inside the root (links leaving it are always refused)\n\nEnv: `XI_SOURCES_<NAME>_FOLLOW_SYMLINKS`",
          "title": "XI_SOURCES_<NAME>_FOLLOW_SYMLINKS",
          "type": "boolean"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "root": {
          "description": "fs root (jail via os.Root); ftp/sftp: server directory keys are relative to (default /)\n\nEnv: XI_SOURCES_<NAME>_ROOT",
          "markdownDescription": "fs root (jail via `os.Root`); ftp/sftp: server directory keys are relative to (default `/`)\n\nEnv: `XI_SOURCES_<NAME>_ROOT`",
          "title": "XI_SOURCES_<NAME>_ROOT",
          "type": "string"
        },
        "type": {
          "const": "fs",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        }
      },
      "required": [
        "type"
      ],
      "title": "fs source",
      "type": "object"
    },
    "source_ftp": {
      "additionalProperties": false,
      "properties": {
        "dial_timeout": {
          "$ref": "#/$defs/duration",
          "description": "ftp/sftp: connect + TLS/SSH handshake + login\n\nEnv: XI_SOURCES_<NAME>_DIAL_TIMEOUT",
          "markdownDescription": "ftp/sftp: connect + TLS/SSH handshake + login\n\nEnv: `XI_SOURCES_<NAME>_DIAL_TIMEOUT`",
          "title": "XI_SOURCES_<NAME>_DIAL_TIMEOUT"
        },
        "host": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_HOST",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_HOST`",
          "title": "XI_SOURCES_<NAME>_HOST",
          "type": "string"
        },
        "idle_timeout": {
          "$ref": "#/$defs/duration",
          "description": "ftp/sftp: pooled connections idle longer are closed\n\nEnv: XI_SOURCES_<NAME>_IDLE_TIMEOUT",
          "markdownDescription": "ftp/sftp: pooled connections idle longer are closed\n\nEnv: `XI_SOURCES_<NAME>_IDLE_TIMEOUT`",
          "title": "XI_SOURCES_<NAME>_IDLE_TIMEOUT"
        },
        "password": {
          "$ref": "#/$defs/secret",
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PASSWORD (or XI_SOURCES_<NAME>_PASSWORD_FILE)",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PASSWORD` (or `XI_SOURCES_<NAME>_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PASSWORD"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "pool_size": {
          "description": "ftp/sftp: max open connections to the server\n\nEnv: XI_SOURCES_<NAME>_POOL_SIZE",
          "markdownDescription": "ftp/sftp: max open connections to the server\n\nEnv: `XI_SOURCES_<NAME>_POOL_SIZE`",
          "title": "XI_SOURCES_<NAME>_POOL_SIZE",
          "type": "integer"
        },
        "port": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PORT",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PORT`",
          "title": "XI_SOURCES_<NAME>_PORT",
          "type": "integer"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "root": {
          "description": "fs root (jail via os.Root); ftp/sftp: server directory keys are relative to (default /)\n\nEnv: XI_SOURCES_<NAME>_ROOT",
          "markdownDescription": "fs root (jail via `os.Root`); ftp/sftp: server directory keys are relative to (default `/`)\n\nEnv: `XI_SOURCES_<NAME>_ROOT`",
          "title": "XI_SOURCES_<NAME>_ROOT",
          "type": "string"
        },
        "tls": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_TLS",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_TLS`",
          "title": "XI_SOURCES_<NAME>_TLS",
          "type": "boolean"
        },
        "type": {
          "const": "ftp",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        },
        "user": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_USER",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_USER`",
          "title": "XI_SOURCES_<NAME>_USER",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "title": "ftp source",
      "type": "object"
    },
    "source_gcs": {
      "additionalProperties": false,
      "properties": {
        "bucket": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKET",
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKET`",
          "title": "XI_SOURCES_<NAME>_BUCKET",
          "type": "string"
        },
        "buckets": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKETS",
          "items": {
            "type": "string"
          },
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKETS`",
          "title": "XI_SOURCES_<NAME>_BUCKETS",
          "type": "array"
        },
        "credentials_json": {
          "$ref": "#/$defs/secret",
          "description": "gcs: credentials document (XI_SOURCES_<NAME>_CREDENTIALS_JSON_FILE reads it from a mounted file); XI_SOURCES_<NAME>_ENDPOINT points at an emulator such as fake-gcs-server\n\nDefault: ADC\n\nEnv: XI_SOURCES_<NAME>_CREDENTIALS_JSON (or XI_SOURCES_<NAME>_CREDENTIALS_JSON_FILE)",
          "markdownDescription": "gcs: credentials document (`XI_SOURCES_<NAME>_CREDENTIALS_JSON_FILE` reads it from a mounted file); `XI_SOURCES_<NAME>_ENDPOINT` points at an emulator such as fake-gcs-server\n\nDefault: ADC\n\nEnv: `XI_SOURCES_<NAME>_CREDENTIALS_JSON` (or `XI_SOURCES_<NAME>_CREDENTIALS_JSON_FILE`)",
          "title": "XI_SOURCES_<NAME>_CREDENTIALS_JSON"
        },
        "endpoint": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_ENDPOINT",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_ENDPOINT`",
          "title": "XI_SOURCES_<NAME>_ENDPOINT",
          "type": "string"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "type": {
          "const": "gcs",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        }
      },
      "required": [
        "type"
      ],
      "title": "gcs source",
      "type": "object"
    },
    "source_http": {
      "additionalProperties": false,
      "properties": {
        "allow": {
          "description": "http: host globs with optional scheme, port and path prefix (*.example.com, cdn.example.com/img/, https://*, *); a bare https://… source and xi://<name>/https://… must match one. An http source needs _ALLOW, _BASE_URL or both\n\nEnv: XI_SOURCES_<NAME>_ALLOW",
          "items": {
            "type": "string"
          },
          "markdownDescription": "http: host globs with optional scheme, port and path prefix (`*.example.com`, `cdn.example.com/img/`, `https://*`, `*`); a bare `https://…` source and `xi://<name>/https://…` must match one. An http source needs `_ALLOW`, `_BASE_URL` or both\n\nEnv: `XI_SOURCES_<NAME>_ALLOW`",
          "title": "XI_SOURCES_<NAME>_ALLOW",
          "type": "array"
        },
        "base_url": {
          "description": "http: base for relative keys (xi://<name>/a.jpg → <base>/a.jpg), implicitly allowed. Without it, xi://<name>/<key> takes only an absolute http(s):// key, checked against _ALLOW; with it, an absolute key under the base is taken as is and any other key is joined to the base ([URL-DIALECTS §3.1](URL-DIALECTS.md#31-grammar))\n\nDefault: — (optional)\n\nEnv: XI_SOURCES_<NAME>_BASE_URL",
          "markdownDescription": "http: base for relative keys (`xi://<name>/a.jpg` → `<base>/a.jpg`), implicitly allowed. Without it, `xi://<name>/<key>` takes only an absolute `http(s)://` key, checked against `_ALLOW`; with it, an absolute key under the base is taken as is and any other key is joined to the base ([URL-DIALECTS §3.1](URL-DIALECTS.md#31-grammar))\n\nDefault: — (optional)\n\nEnv: `XI_SOURCES_<NAME>_BASE_URL`",
          "title": "XI_SOURCES_<NAME>_BASE_URL",
          "type": "string"
        },
        "headers": {
          "additionalProperties": {
            "$ref": "#/$defs/secret"
          },
          "description": "http: extra request headers (Authorization=…)\n\nEnv: XI_SOURCES_<NAME>_HEADERS (or XI_SOURCES_<NAME>_HEADERS_FILE)",
          "markdownDescription": "http: extra request headers (`Authorization=…`)\n\nEnv: `XI_SOURCES_<NAME>_HEADERS` (or `XI_SOURCES_<NAME>_HEADERS_FILE`)",
          "title": "XI_SOURCES_<NAME>_HEADERS",
          "type": "object"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "type": {
          "const": "http",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        }
      },
      "required": [
        "type"
      ],
      "title": "http source",
      "type": "object"
    },
    "source_s3": {
      "additionalProperties": false,
      "properties": {
        "access_key_id": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_ACCESS_KEY_ID",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_ACCESS_KEY_ID`",
          "title": "XI_SOURCES_<NAME>_ACCESS_KEY_ID",
          "type": "string"
        },
        "bucket": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKET",
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKET`",
          "title": "XI_SOURCES_<NAME>_BUCKET",
          "type": "string"
        },
        "buckets": {
          "description": "s3/gcs/azure: allowed buckets/containers; _BUCKET binds a default\n\nEnv: XI_SOURCES_<NAME>_BUCKETS",
          "items": {
            "type": "string"
          },
          "markdownDescription": "s3/gcs/azure: allowed buckets/containers; `_BUCKET` binds a default\n\nEnv: `XI_SOURCES_<NAME>_BUCKETS`",
          "title": "XI_SOURCES_<NAME>_BUCKETS",
          "type": "array"
        },
        "endpoint": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_ENDPOINT",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_ENDPOINT`",
          "title": "XI_SOURCES_<NAME>_ENDPOINT",
          "type": "string"
        },
        "force_path_style": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_FORCE_PATH_STYLE",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_FORCE_PATH_STYLE`",
          "title": "XI_SOURCES_<NAME>_FORCE_PATH_STYLE",
          "type": "boolean"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "region": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_REGION",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_REGION`",
          "title": "XI_SOURCES_<NAME>_REGION",
          "type": "string"
        },
        "role_arn": {
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_ROLE_ARN",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_ROLE_ARN`",
          "title": "XI_SOURCES_<NAME>_ROLE_ARN",
          "type": "string"
        },
        "secret_access_key": {
          "$ref": "#/$defs/secret",
          "description": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); _ROLE_ARN assumes a role on top. _REGION falls back to the SDK chain, then us-east-1 when an endpoint is set (R2: auto). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: XI_SOURCES_<NAME>_SECRET_ACCESS_KEY (or XI_SOURCES_<NAME>_SECRET_ACCESS_KEY_FILE)",
          "markdownDescription": "s3 (R2, Spaces, B2, RustFS and other S3-compatible stores via endpoint). No keys → AWS default chain (env, shared files, IRSA, ECS/EC2 role); `_ROLE_ARN` assumes a role on top. `_REGION` falls back to the SDK chain, then `us-east-1` when an endpoint is set (R2: `auto`). Checksums are sent/validated only when required, so R2/B2 work\n\nEnv: `XI_SOURCES_<NAME>_SECRET_ACCESS_KEY` (or `XI_SOURCES_<NAME>_SECRET_ACCESS_KEY_FILE`)",
          "title": "XI_SOURCES_<NAME>_SECRET_ACCESS_KEY"
        },
        "type": {
          "const": "s3",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        }
      },
      "required": [
        "type"
      ],
      "title": "s3 source",
      "type": "object"
    },
    "source_sftp": {
      "additionalProperties": false,
      "properties": {
        "dial_timeout": {
          "$ref": "#/$defs/duration",
          "description": "ftp/sftp: connect + TLS/SSH handshake + login\n\nEnv: XI_SOURCES_<NAME>_DIAL_TIMEOUT",
          "markdownDescription": "ftp/sftp: connect + TLS/SSH handshake + login\n\nEnv: `XI_SOURCES_<NAME>_DIAL_TIMEOUT`",
          "title": "XI_SOURCES_<NAME>_DIAL_TIMEOUT"
        },
        "host": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_HOST",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_HOST`",
          "title": "XI_SOURCES_<NAME>_HOST",
          "type": "string"
        },
        "host_key": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_HOST_KEY",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_HOST_KEY`",
          "title": "XI_SOURCES_<NAME>_HOST_KEY",
          "type": "string"
        },
        "idle_timeout": {
          "$ref": "#/$defs/duration",
          "description": "ftp/sftp: pooled connections idle longer are closed\n\nEnv: XI_SOURCES_<NAME>_IDLE_TIMEOUT",
          "markdownDescription": "ftp/sftp: pooled connections idle longer are closed\n\nEnv: `XI_SOURCES_<NAME>_IDLE_TIMEOUT`",
          "title": "XI_SOURCES_<NAME>_IDLE_TIMEOUT"
        },
        "password": {
          "$ref": "#/$defs/secret",
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PASSWORD (or XI_SOURCES_<NAME>_PASSWORD_FILE)",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PASSWORD` (or `XI_SOURCES_<NAME>_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PASSWORD"
        },
        "pdf_password": {
          "$ref": "#/$defs/secret",
          "description": "every type: default password for password-protected PDFs of this source, used when a request carries no pdf_password of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (_PDF_PASSWORD_FILE, {file: …}), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: XI_SOURCES_<NAME>_PDF_PASSWORD (or XI_SOURCES_<NAME>_PDF_PASSWORD_FILE)",
          "markdownDescription": "every type: default password for password-protected PDFs of this source, used when a request carries no `pdf_password` of its own ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); a secret (`_PDF_PASSWORD_FILE`, `{file: …}`), at most 1024 bytes without control characters. Rotating it does not purge renditions already in the result cache\n\nEnv: `XI_SOURCES_<NAME>_PDF_PASSWORD` (or `XI_SOURCES_<NAME>_PDF_PASSWORD_FILE`)",
          "title": "XI_SOURCES_<NAME>_PDF_PASSWORD"
        },
        "pool_size": {
          "description": "ftp/sftp: max open connections to the server\n\nEnv: XI_SOURCES_<NAME>_POOL_SIZE",
          "markdownDescription": "ftp/sftp: max open connections to the server\n\nEnv: `XI_SOURCES_<NAME>_POOL_SIZE`",
          "title": "XI_SOURCES_<NAME>_POOL_SIZE",
          "type": "integer"
        },
        "port": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PORT",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PORT`",
          "title": "XI_SOURCES_<NAME>_PORT",
          "type": "integer"
        },
        "prefix": {
          "description": "object key prefix jail (every type except http)\n\nEnv: XI_SOURCES_<NAME>_PREFIX",
          "markdownDescription": "object key prefix jail (every type except http)\n\nEnv: `XI_SOURCES_<NAME>_PREFIX`",
          "title": "XI_SOURCES_<NAME>_PREFIX",
          "type": "string"
        },
        "private_key": {
          "$ref": "#/$defs/secret",
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PRIVATE_KEY (or XI_SOURCES_<NAME>_PRIVATE_KEY_FILE)",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PRIVATE_KEY` (or `XI_SOURCES_<NAME>_PRIVATE_KEY_FILE`)",
          "title": "XI_SOURCES_<NAME>_PRIVATE_KEY"
        },
        "private_key_passphrase": {
          "$ref": "#/$defs/secret",
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_PRIVATE_KEY_PASSPHRASE (or XI_SOURCES_<NAME>_PRIVATE_KEY_PASSPHRASE_FILE)",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_PRIVATE_KEY_PASSPHRASE` (or `XI_SOURCES_<NAME>_PRIVATE_KEY_PASSPHRASE_FILE`)",
          "title": "XI_SOURCES_<NAME>_PRIVATE_KEY_PASSPHRASE"
        },
        "root": {
          "description": "fs root (jail via os.Root); ftp/sftp: server directory keys are relative to (default /)\n\nEnv: XI_SOURCES_<NAME>_ROOT",
          "markdownDescription": "fs root (jail via `os.Root`); ftp/sftp: server directory keys are relative to (default `/`)\n\nEnv: `XI_SOURCES_<NAME>_ROOT`",
          "title": "XI_SOURCES_<NAME>_ROOT",
          "type": "string"
        },
        "type": {
          "const": "sftp",
          "description": "http | s3 | gcs | azure | fs | ftp | sftp\n\nEnv: XI_SOURCES_<NAME>_TYPE",
          "markdownDescription": "`http` | `s3` | `gcs` | `azure` | `fs` | `ftp` | `sftp`\n\nEnv: `XI_SOURCES_<NAME>_TYPE`",
          "title": "XI_SOURCES_<NAME>_TYPE"
        },
        "user": {
          "description": "ftp/sftp. ftp: empty _USER logs in as anonymous; _TLS=true is explicit TLS (AUTH TLS, PROT P), passive mode only. sftp: _USER plus _PASSWORD and/or _PRIVATE_KEY (PEM/OpenSSH; encrypted keys need _PRIVATE_KEY_PASSPHRASE); _HOST_KEY is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, # comments; no trust-on-first-use). _HOST is a bare host name or address (no scheme or path). config validate parses the private key and every host key line\n\nEnv: XI_SOURCES_<NAME>_USER",
          "markdownDescription": "ftp/sftp. ftp: empty `_USER` logs in as `anonymous`; `_TLS=true` is explicit TLS (`AUTH TLS`, `PROT P`), passive mode only. sftp: `_USER` plus `_PASSWORD` and/or `_PRIVATE_KEY` (PEM/OpenSSH; encrypted keys need `_PRIVATE_KEY_PASSPHRASE`); `_HOST_KEY` is mandatory (authorized_keys or known_hosts lines, several allowed for rotation, `#` comments; no trust-on-first-use). `_HOST` is a bare host name or address (no scheme or path). `config validate` parses the private key and every host key line\n\nEnv: `XI_SOURCES_<NAME>_USER`",
          "title": "XI_SOURCES_<NAME>_USER",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "title": "sftp source",
      "type": "object"
    },
    "tristate": {
      "description": "true, false, or auto (derived from other settings).",
      "oneOf": [
        {
          "type": "boolean"
        },
        {
          "const": "auto"
        }
      ]
    },
    "videoSecond": {
      "description": "A timestamp in seconds (negative counts from the end of the video), or auto for best-frame selection.",
      "examples": [
        "auto",
        1,
        -2.5
      ],
      "oneOf": [
        {
          "type": "number"
        },
        {
          "pattern": "^\\s*([Aa][Uu][Tt][Oo]|[-+]?([0-9]+\\.?[0-9]*|\\.[0-9]+)([eE][-+]?[0-9]+)?)\\s*$",
          "type": "string"
        }
      ]
    }
  },
  "$id": "https://xantiagoma.github.io/xtandard-image/schema/v1/config.schema.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "description": "Config file of xtandard-image (YAML, JSON or TOML). Every key maps to an XI_* environment variable, which wins over the file. See docs/CONFIG.md.",
  "properties": {
    "$schema": {
      "description": "URL of this JSON Schema, for editors; the loader ignores it.",
      "type": "string"
    },
    "admin": {
      "additionalProperties": false,
      "properties": {
        "addr": {
          "default": "127.0.0.1:9090",
          "description": "Admin listener: /metrics, /openapi.json, pprof; set :9090 for in-cluster scraping; empty disables\n\nEnv: XI_ADMIN_ADDR",
          "markdownDescription": "Admin listener: `/metrics`, `/openapi.json`, pprof; set `:9090` for in-cluster scraping; empty disables\n\nEnv: `XI_ADMIN_ADDR`",
          "title": "XI_ADMIN_ADDR",
          "type": "string"
        },
        "docs": {
          "default": false,
          "description": "Scalar API reference at admin /docs, loaded by the browser from a pinned cdn.jsdelivr.net URL (SRI, strict CSP); off answers 404 and points at /openapi.json ([OPENAPI.md](OPENAPI.md))\n\nEnv: XI_ADMIN_DOCS",
          "markdownDescription": "Scalar API reference at admin `/docs`, loaded by the browser from a pinned `cdn.jsdelivr.net` URL (SRI, strict CSP); off answers 404 and points at `/openapi.json` ([OPENAPI.md](OPENAPI.md))\n\nEnv: `XI_ADMIN_DOCS`",
          "title": "XI_ADMIN_DOCS",
          "type": "boolean"
        },
        "pprof": {
          "default": false,
          "description": "pprof incl. goroutineleak on the admin listener\n\nEnv: XI_ADMIN_PPROF",
          "markdownDescription": "pprof incl. `goroutineleak` on the admin listener\n\nEnv: `XI_ADMIN_PPROF`",
          "title": "XI_ADMIN_PPROF",
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "cache": {
      "additionalProperties": false,
      "properties": {
        "allow_bypass": {
          "default": false,
          "description": "Honour bypass_cache (the request skips the result cache: no lookup, no store)\n\nEnv: XI_CACHE_ALLOW_BYPASS",
          "markdownDescription": "Honour `bypass_cache` (the request skips the result cache: no lookup, no store)\n\nEnv: `XI_CACHE_ALLOW_BYPASS`",
          "title": "XI_CACHE_ALLOW_BYPASS",
          "type": "boolean"
        },
        "etag_buster": {
          "$ref": "#/$defs/secret",
          "description": "Global ETag salt\n\nEnv: XI_CACHE_ETAG_BUSTER (or XI_CACHE_ETAG_BUSTER_FILE)",
          "markdownDescription": "Global ETag salt\n\nEnv: `XI_CACHE_ETAG_BUSTER` (or `XI_CACHE_ETAG_BUSTER_FILE`)",
          "title": "XI_CACHE_ETAG_BUSTER"
        },
        "headers": {
          "additionalProperties": false,
          "properties": {
            "error_ttl": {
              "$ref": "#/$defs/duration",
              "default": "10s",
              "description": "For upstream failures\n\nEnv: XI_CACHE_HEADERS_ERROR_TTL",
              "markdownDescription": "For upstream failures\n\nEnv: `XI_CACHE_HEADERS_ERROR_TTL`",
              "title": "XI_CACHE_HEADERS_ERROR_TTL"
            },
            "passthrough": {
              "default": false,
              "description": "Use source Cache-Control/Expires\n\nEnv: XI_CACHE_HEADERS_PASSTHROUGH",
              "markdownDescription": "Use source Cache-Control/Expires\n\nEnv: `XI_CACHE_HEADERS_PASSTHROUGH`",
              "title": "XI_CACHE_HEADERS_PASSTHROUGH",
              "type": "boolean"
            },
            "ttl": {
              "$ref": "#/$defs/duration",
              "default": "8760h0m0s",
              "description": "max-age for successes\n\nEnv: XI_CACHE_HEADERS_TTL",
              "markdownDescription": "`max-age` for successes\n\nEnv: `XI_CACHE_HEADERS_TTL`",
              "title": "XI_CACHE_HEADERS_TTL"
            }
          },
          "type": "object"
        },
        "origin": {
          "additionalProperties": false,
          "properties": {
            "memory_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "256MiB",
              "description": "Original-bytes LRU\n\nEnv: XI_CACHE_ORIGIN_MEMORY_BYTES",
              "markdownDescription": "Original-bytes LRU\n\nEnv: `XI_CACHE_ORIGIN_MEMORY_BYTES`",
              "title": "XI_CACHE_ORIGIN_MEMORY_BYTES"
            },
            "negative_ttl": {
              "$ref": "#/$defs/duration",
              "default": "5s",
              "description": "How long a source not-found is remembered; 0 disables\n\nEnv: XI_CACHE_ORIGIN_NEGATIVE_TTL",
              "markdownDescription": "How long a source not-found is remembered; `0` disables\n\nEnv: `XI_CACHE_ORIGIN_NEGATIVE_TTL`",
              "title": "XI_CACHE_ORIGIN_NEGATIVE_TTL"
            },
            "store_ttl": {
              "$ref": "#/$defs/duration",
              "default": "10m0s",
              "description": "How long an original is served from XI_CACHE_ORIGIN_STORES without contacting the source (no revalidation)\n\nEnv: XI_CACHE_ORIGIN_STORE_TTL",
              "markdownDescription": "How long an original is served from `XI_CACHE_ORIGIN_STORES` without contacting the source (no revalidation)\n\nEnv: `XI_CACHE_ORIGIN_STORE_TTL`",
              "title": "XI_CACHE_ORIGIN_STORE_TTL"
            },
            "stores": {
              "description": "Shared stores behind the origin LRU (disk, redis, s3, fastest first; configured by the XI_CACHE_RESULT_* variables of the same store, which the result cache shares under another namespace): a replica that misses its LRU reads the original from them before downloading, and downloads are written to them. Empty: originals stay in-process\n\nEnv: XI_CACHE_ORIGIN_STORES",
              "items": {
                "enum": [
                  "memory",
                  "disk",
                  "redis",
                  "s3"
                ],
                "type": "string"
              },
              "markdownDescription": "Shared stores behind the origin LRU (`disk`, `redis`, `s3`, fastest first; configured by the `XI_CACHE_RESULT_*` variables of the same store, which the result cache shares under another namespace): a replica that misses its LRU reads the original from them before downloading, and downloads are written to them. Empty: originals stay in-process\n\nEnv: `XI_CACHE_ORIGIN_STORES`",
              "title": "XI_CACHE_ORIGIN_STORES",
              "type": "array"
            },
            "ttl": {
              "$ref": "#/$defs/duration",
              "default": "1m0s",
              "description": "After it, entries with an ETag/Last-Modified are revalidated with a conditional request (304 refreshes the TTL)\n\nEnv: XI_CACHE_ORIGIN_TTL",
              "markdownDescription": "After it, entries with an ETag/Last-Modified are revalidated with a conditional request (304 refreshes the TTL)\n\nEnv: `XI_CACHE_ORIGIN_TTL`",
              "title": "XI_CACHE_ORIGIN_TTL"
            }
          },
          "type": "object"
        },
        "result": {
          "additionalProperties": false,
          "properties": {
            "disk": {
              "additionalProperties": false,
              "properties": {
                "bytes": {
                  "$ref": "#/$defs/byteSize",
                  "default": "10GiB",
                  "description": "Absolute directory of the disk store and its size bound (entry files; a janitor evicts least recently used entries down to 90 % once a write goes over). The image's root file system is read-only: mount a volume there. The directory must be dedicated to one process; the index is rebuilt from it at start, so entries survive restarts. No fsync: a file torn by a crash fails its checksum and is dropped\n\nEnv: XI_CACHE_RESULT_DISK_BYTES",
                  "markdownDescription": "Absolute directory of the `disk` store and its size bound (entry files; a janitor evicts least recently used entries down to 90 % once a write goes over). The image's root file system is read-only: mount a volume there. The directory must be dedicated to one process; the index is rebuilt from it at start, so entries survive restarts. No fsync: a file torn by a crash fails its checksum and is dropped\n\nEnv: `XI_CACHE_RESULT_DISK_BYTES`",
                  "title": "XI_CACHE_RESULT_DISK_BYTES"
                },
                "path": {
                  "default": "",
                  "description": "Absolute directory of the disk store and its size bound (entry files; a janitor evicts least recently used entries down to 90 % once a write goes over). The image's root file system is read-only: mount a volume there. The directory must be dedicated to one process; the index is rebuilt from it at start, so entries survive restarts. No fsync: a file torn by a crash fails its checksum and is dropped\n\nEnv: XI_CACHE_RESULT_DISK_PATH",
                  "markdownDescription": "Absolute directory of the `disk` store and its size bound (entry files; a janitor evicts least recently used entries down to 90 % once a write goes over). The image's root file system is read-only: mount a volume there. The directory must be dedicated to one process; the index is rebuilt from it at start, so entries survive restarts. No fsync: a file torn by a crash fails its checksum and is dropped\n\nEnv: `XI_CACHE_RESULT_DISK_PATH`",
                  "title": "XI_CACHE_RESULT_DISK_PATH",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "memory_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "256MiB",
              "description": "In-memory LRU tier bound (bodies, stored headers and keys)\n\nEnv: XI_CACHE_RESULT_MEMORY_BYTES",
              "markdownDescription": "In-memory LRU tier bound (bodies, stored headers and keys)\n\nEnv: `XI_CACHE_RESULT_MEMORY_BYTES`",
              "title": "XI_CACHE_RESULT_MEMORY_BYTES"
            },
            "redis": {
              "additionalProperties": false,
              "properties": {
                "max_value_bytes": {
                  "$ref": "#/$defs/byteSize",
                  "default": "1MiB",
                  "description": "Larger entries are not stored in Redis (counted in xi_cache_skipped_total{reason=\"too_large\"})\n\nEnv: XI_CACHE_RESULT_REDIS_MAX_VALUE_BYTES",
                  "markdownDescription": "Larger entries are not stored in Redis (counted in `xi_cache_skipped_total{reason=\"too_large\"}`)\n\nEnv: `XI_CACHE_RESULT_REDIS_MAX_VALUE_BYTES`",
                  "title": "XI_CACHE_RESULT_REDIS_MAX_VALUE_BYTES"
                },
                "prefix": {
                  "default": "xi:",
                  "description": "Prepended to every key, so deployments can share a server\n\nEnv: XI_CACHE_RESULT_REDIS_PREFIX",
                  "markdownDescription": "Prepended to every key, so deployments can share a server\n\nEnv: `XI_CACHE_RESULT_REDIS_PREFIX`",
                  "title": "XI_CACHE_RESULT_REDIS_PREFIX",
                  "type": "string"
                },
                "url": {
                  "$ref": "#/$defs/secret",
                  "description": "Secret (may carry a password). redis://[user:pass@]host:port/db, rediss:// for TLS, go-redis query options (dial_timeout, pool_size, …). Any RESP server: Redis, Valkey, KeyDB, managed services\n\nEnv: XI_CACHE_RESULT_REDIS_URL (or XI_CACHE_RESULT_REDIS_URL_FILE)",
                  "markdownDescription": "Secret (may carry a password). `redis://[user:pass@]host:port/db`, `rediss://` for TLS, go-redis query options (`dial_timeout`, `pool_size`, …). Any RESP server: Redis, Valkey, KeyDB, managed services\n\nEnv: `XI_CACHE_RESULT_REDIS_URL` (or `XI_CACHE_RESULT_REDIS_URL_FILE`)",
                  "title": "XI_CACHE_RESULT_REDIS_URL"
                }
              },
              "type": "object"
            },
            "s3": {
              "additionalProperties": false,
              "properties": {
                "bucket": {
                  "default": "",
                  "description": "SOURCE names an s3 source whose endpoint, region, path style and credentials the s3 store reuses; BUCKET (required, must exist) and PREFIX (prepended verbatim, e.g. cache/) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: XI_CACHE_RESULT_S3_BUCKET",
                  "markdownDescription": "`SOURCE` names an s3 source whose endpoint, region, path style and credentials the `s3` store reuses; `BUCKET` (required, must exist) and `PREFIX` (prepended verbatim, e.g. `cache/`) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: `XI_CACHE_RESULT_S3_BUCKET`",
                  "title": "XI_CACHE_RESULT_S3_BUCKET",
                  "type": "string"
                },
                "prefix": {
                  "default": "",
                  "description": "SOURCE names an s3 source whose endpoint, region, path style and credentials the s3 store reuses; BUCKET (required, must exist) and PREFIX (prepended verbatim, e.g. cache/) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: XI_CACHE_RESULT_S3_PREFIX",
                  "markdownDescription": "`SOURCE` names an s3 source whose endpoint, region, path style and credentials the `s3` store reuses; `BUCKET` (required, must exist) and `PREFIX` (prepended verbatim, e.g. `cache/`) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: `XI_CACHE_RESULT_S3_PREFIX`",
                  "title": "XI_CACHE_RESULT_S3_PREFIX",
                  "type": "string"
                },
                "source": {
                  "default": "",
                  "description": "SOURCE names an s3 source whose endpoint, region, path style and credentials the s3 store reuses; BUCKET (required, must exist) and PREFIX (prepended verbatim, e.g. cache/) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: XI_CACHE_RESULT_S3_SOURCE",
                  "markdownDescription": "`SOURCE` names an s3 source whose endpoint, region, path style and credentials the `s3` store reuses; `BUCKET` (required, must exist) and `PREFIX` (prepended verbatim, e.g. `cache/`) place the objects. Expiry is checked from object metadata on read, so no lifecycle rule is needed for correctness; add one on the prefix to reclaim space\n\nEnv: `XI_CACHE_RESULT_S3_SOURCE`",
                  "title": "XI_CACHE_RESULT_S3_SOURCE",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "stores": {
              "description": "Ordered tiers, fastest first, e.g. memory,disk or memory,redis,s3; empty disables the result cache. A hit in a slower tier is copied into the faster ones (ARCHITECTURE §9.2)\n\nEnv: XI_CACHE_RESULT_STORES",
              "items": {
                "enum": [
                  "memory",
                  "disk",
                  "redis",
                  "s3"
                ],
                "type": "string"
              },
              "markdownDescription": "Ordered tiers, fastest first, e.g. `memory,disk` or `memory,redis,s3`; empty disables the result cache. A hit in a slower tier is copied into the faster ones (ARCHITECTURE §9.2)\n\nEnv: `XI_CACHE_RESULT_STORES`",
              "title": "XI_CACHE_RESULT_STORES",
              "type": "array"
            },
            "ttl": {
              "$ref": "#/$defs/duration",
              "default": "24h0m0s",
              "description": "How long a result is served from the cache without contacting the source (the key holds the source identity, not its validators); a per-request ttl/expires shortens it. Use a cachebuster or XI_CACHE_ETAG_BUSTER to invalidate earlier\n\nEnv: XI_CACHE_RESULT_TTL",
              "markdownDescription": "How long a result is served from the cache without contacting the source (the key holds the source identity, not its validators); a per-request `ttl`/`expires` shortens it. Use a `cachebuster` or `XI_CACHE_ETAG_BUSTER` to invalidate earlier\n\nEnv: `XI_CACHE_RESULT_TTL`",
              "title": "XI_CACHE_RESULT_TTL"
            },
            "write_queue": {
              "default": 64,
              "description": "Asynchronous cache writes in flight; more are dropped and counted in xi_cache_writes_dropped_total, never blocking the response\n\nEnv: XI_CACHE_RESULT_WRITE_QUEUE",
              "markdownDescription": "Asynchronous cache writes in flight; more are dropped and counted in `xi_cache_writes_dropped_total`, never blocking the response\n\nEnv: `XI_CACHE_RESULT_WRITE_QUEUE`",
              "title": "XI_CACHE_RESULT_WRITE_QUEUE",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "tags_header": {
          "default": "Cache-Tag",
          "description": "For cache_tags\n\nEnv: XI_CACHE_TAGS_HEADER",
          "markdownDescription": "For `cache_tags`\n\nEnv: `XI_CACHE_TAGS_HEADER`",
          "title": "XI_CACHE_TAGS_HEADER",
          "type": "string"
        }
      },
      "type": "object"
    },
    "client_hints": {
      "additionalProperties": false,
      "properties": {
        "critical_ch": {
          "default": false,
          "description": "Also send Critical-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width (browsers honour it on navigations only)\n\nEnv: XI_CLIENT_HINTS_CRITICAL_CH",
          "markdownDescription": "Also send `Critical-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width` (browsers honour it on navigations only)\n\nEnv: `XI_CLIENT_HINTS_CRITICAL_CH`",
          "title": "XI_CLIENT_HINTS_CRITICAL_CH",
          "type": "boolean"
        },
        "enabled": {
          "default": false,
          "description": "Honour DPR/Width/Viewport-Width/Save-Data hints on every request when the URL leaves the value unset (imgproxy IMGPROXY_ENABLE_CLIENT_HINTS); URL opt-ins (ch=, w_auto, width=auto) work regardless. Image responses then carry Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width, ECT and a Vary listing only the hint names that decided the result. Unsigned requests on a restricted mount get only Save-Data (a hinted width/DPR would bypass the allowlists). Semantics and imgproxy parity: URL-DIALECTS.md §3.5\n\nEnv: XI_CLIENT_HINTS_ENABLED",
          "markdownDescription": "Honour DPR/Width/Viewport-Width/Save-Data hints on every request when the URL leaves the value unset (imgproxy `IMGPROXY_ENABLE_CLIENT_HINTS`); URL opt-ins (`ch=`, `w_auto`, `width=auto`) work regardless. Image responses then carry `Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width, ECT` and a `Vary` listing only the hint names that decided the result. Unsigned requests on a `restricted` mount get only Save-Data (a hinted width/DPR would bypass the allowlists). Semantics and imgproxy parity: URL-DIALECTS.md §3.5\n\nEnv: `XI_CLIENT_HINTS_ENABLED`",
          "title": "XI_CLIENT_HINTS_ENABLED",
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "compat": {
      "additionalProperties": false,
      "properties": {
        "imgproxy_env": {
          "default": false,
          "description": "Translate IMGPROXY_* variables (§1, table below); YAML compat.imgproxy_env\n\nEnv: XI_COMPAT_IMGPROXY_ENV",
          "markdownDescription": "Translate `IMGPROXY_*` variables (§1, table below); YAML `compat.imgproxy_env`\n\nEnv: `XI_COMPAT_IMGPROXY_ENV`",
          "title": "XI_COMPAT_IMGPROXY_ENV",
          "type": "boolean"
        },
        "imgproxy_status": {
          "default": false,
          "description": "Invalid URL and disallowed source answer 404 like imgproxy (instead of 400 / 403)\n\nEnv: XI_COMPAT_IMGPROXY_STATUS",
          "markdownDescription": "Invalid URL and disallowed source answer 404 like imgproxy (instead of 400 / 403)\n\nEnv: `XI_COMPAT_IMGPROXY_STATUS`",
          "title": "XI_COMPAT_IMGPROXY_STATUS",
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "detect": {
      "additionalProperties": false,
      "properties": {
        "cache_size": {
          "default": 512,
          "description": "Detection results kept in memory, least recently used first out: one entry per model and source (keyed by the SHA-256 of the source bytes), shared by processing and /info (0–1000000; 0 disables the cache)\n\nEnv: XI_DETECT_CACHE_SIZE",
          "markdownDescription": "Detection results kept in memory, least recently used first out: one entry per model and source (keyed by the SHA-256 of the source bytes), shared by processing and `/info` (0–1000000; `0` disables the cache)\n\nEnv: `XI_DETECT_CACHE_SIZE`",
          "title": "XI_DETECT_CACHE_SIZE",
          "type": "integer"
        },
        "classify": {
          "additionalProperties": false,
          "properties": {
            "labels": {
              "description": "Absolute path of the class names file of a custom classifier: one name per line, in output order (imgproxy's IMGPROXY_CLASSIFICATION_CLASSES format); empty uses the embedded ImageNet-1k names (imagenet-simple-labels, public domain), which need a 1000-class output\n\nDefault: *(empty)*\n\nEnv: XI_DETECT_CLASSIFY_LABELS",
              "markdownDescription": "Absolute path of the class names file of a custom classifier: one name per line, in output order (imgproxy's `IMGPROXY_CLASSIFICATION_CLASSES` format); empty uses the embedded ImageNet-1k names (imagenet-simple-labels, public domain), which need a 1000-class output\n\nDefault: *(empty)*\n\nEnv: `XI_DETECT_CLASSIFY_LABELS`",
              "title": "XI_DETECT_CLASSIFY_LABELS",
              "type": "string"
            },
            "model": {
              "default": "mobilenetv3_large_100.onnx",
              "description": "Image classifier of the /info option classify (WP-110): an ONNX model with one float32 NCHW or NHWC RGB input and one score per class, a file name in XI_DETECT_MODELS_DIR or an absolute path; empty disables classification. The shipped one is MobileNetV3-Large (timm, ImageNet-1k, Apache-2.0). Its session is created on the first request that classifies, so a deployment that never classifies pays nothing for it. A missing file disables classification only (detection keeps running; classify is then reported in X-Xi-Ignored)\n\nEnv: XI_DETECT_CLASSIFY_MODEL",
              "markdownDescription": "Image classifier of the `/info` option `classify` (WP-110): an ONNX model with one float32 NCHW or NHWC RGB input and one score per class, a file name in `XI_DETECT_MODELS_DIR` or an absolute path; empty disables classification. The shipped one is MobileNetV3-Large (timm, ImageNet-1k, Apache-2.0). Its session is created on the first request that classifies, so a deployment that never classifies pays nothing for it. A missing file disables classification only (detection keeps running; `classify` is then reported in `X-Xi-Ignored`)\n\nEnv: `XI_DETECT_CLASSIFY_MODEL`",
              "title": "XI_DETECT_CLASSIFY_MODEL",
              "type": "string"
            },
            "normalization": {
              "default": "imagenet",
              "description": "How 8-bit input values v are scaled: imagenet ((v/255 − mean)/std with ImageNet statistics: timm and torchvision models), full (v/127.5 − 1, TensorFlow models such as EfficientNet-Lite), half (v/255) or none (0–255). The image is resized so its short side is the input size / 0.875 and centre-cropped (timm's evaluation transform)\n\nEnv: XI_DETECT_CLASSIFY_NORMALIZATION",
              "enum": [
                "imagenet",
                "full",
                "half",
                "none"
              ],
              "markdownDescription": "How 8-bit input values `v` are scaled: `imagenet` (`(v/255 − mean)/std` with ImageNet statistics: timm and torchvision models), `full` (`v/127.5 − 1`, TensorFlow models such as EfficientNet-Lite), `half` (`v/255`) or `none` (0–255). The image is resized so its short side is the input size / 0.875 and centre-cropped (timm's evaluation transform)\n\nEnv: `XI_DETECT_CLASSIFY_NORMALIZATION`",
              "title": "XI_DETECT_CLASSIFY_NORMALIZATION",
              "type": "string"
            },
            "threshold": {
              "default": 0,
              "description": "Classes with a probability below it are left out (0–1). imgproxy's default is 0.5 for its multi-label model; a 1000-class softmax spreads probability over similar classes, so 0 returns the top_k asked for\n\nEnv: XI_DETECT_CLASSIFY_THRESHOLD",
              "markdownDescription": "Classes with a probability below it are left out (0–1). imgproxy's default is 0.5 for its multi-label model; a 1000-class softmax spreads probability over similar classes, so 0 returns the `top_k` asked for\n\nEnv: `XI_DETECT_CLASSIFY_THRESHOLD`",
              "title": "XI_DETECT_CLASSIFY_THRESHOLD",
              "type": "number"
            },
            "top_k_max": {
              "default": 20,
              "description": "Most classes one request gets: cl:<top_k> above it is capped (1–64)\n\nEnv: XI_DETECT_CLASSIFY_TOP_K_MAX",
              "markdownDescription": "Most classes one request gets: `cl:<top_k>` above it is capped (1–64)\n\nEnv: `XI_DETECT_CLASSIFY_TOP_K_MAX`",
              "title": "XI_DETECT_CLASSIFY_TOP_K_MAX",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "enabled": {
          "$ref": "#/$defs/tristate",
          "default": "auto",
          "description": "auto loads detection when the library and the model files are found and runs without it otherwise (logged at startup); true fails startup when they are missing; false never loads them. Options that need detection answer 422 while it is off\n\nEnv: XI_DETECT_ENABLED",
          "markdownDescription": "`auto` loads detection when the library and the model files are found and runs without it otherwise (logged at startup); `true` fails startup when they are missing; `false` never loads them. Options that need detection answer 422 while it is off\n\nEnv: `XI_DETECT_ENABLED`",
          "title": "XI_DETECT_ENABLED"
        },
        "face_model": {
          "default": "face_detection_yunet_2023mar.onnx",
          "description": "YuNet face model, a file name in XI_DETECT_MODELS_DIR or an absolute path; empty disables face detection\n\nEnv: XI_DETECT_FACE_MODEL",
          "markdownDescription": "YuNet face model, a file name in `XI_DETECT_MODELS_DIR` or an absolute path; empty disables face detection\n\nEnv: `XI_DETECT_FACE_MODEL`",
          "title": "XI_DETECT_FACE_MODEL",
          "type": "string"
        },
        "library": {
          "default": "libonnxruntime.so.1",
          "description": "ONNX Runtime shared library (1.17 or newer): an absolute path or a file name the dynamic linker resolves. Loaded with dlopen at startup, never at init (ADR 0010)\n\nEnv: XI_DETECT_LIBRARY",
          "markdownDescription": "ONNX Runtime shared library (1.17 or newer): an absolute path or a file name the dynamic linker resolves. Loaded with `dlopen` at startup, never at init (ADR 0010)\n\nEnv: `XI_DETECT_LIBRARY`",
          "title": "XI_DETECT_LIBRARY",
          "type": "string"
        },
        "max_input": {
          "default": 640,
          "description": "Longest side fed to a model whose input size is dynamic (32–4096); the shipped models are fixed at 640×640\n\nEnv: XI_DETECT_MAX_INPUT",
          "markdownDescription": "Longest side fed to a model whose input size is dynamic (32–4096); the shipped models are fixed at 640×640\n\nEnv: `XI_DETECT_MAX_INPUT`",
          "title": "XI_DETECT_MAX_INPUT",
          "type": "integer"
        },
        "models_dir": {
          "default": "/usr/share/xtandard/models",
          "description": "Absolute directory of the model files; mount your own there\n\nEnv: XI_DETECT_MODELS_DIR",
          "markdownDescription": "Absolute directory of the model files; mount your own there\n\nEnv: `XI_DETECT_MODELS_DIR`",
          "title": "XI_DETECT_MODELS_DIR",
          "type": "string"
        },
        "object_model": {
          "default": "yolox_s.onnx",
          "description": "YOLOX object model (COCO, 80 classes, [1,N,85] output); empty disables object detection\n\nEnv: XI_DETECT_OBJECT_MODEL",
          "markdownDescription": "YOLOX object model (COCO, 80 classes, `[1,N,85]` output); empty disables object detection\n\nEnv: `XI_DETECT_OBJECT_MODEL`",
          "title": "XI_DETECT_OBJECT_MODEL",
          "type": "string"
        },
        "smart_faces": {
          "default": true,
          "description": "Smart gravity (imgproxy sm, imgix crop=edges, Cloudinary g_auto, thumbor smart, …) crops on the detected faces when there are some and falls back to libvips attention otherwise; false always uses attention. Without the face model (slim) smart gravity is attention\n\nEnv: XI_DETECT_SMART_FACES",
          "markdownDescription": "Smart gravity (imgproxy `sm`, imgix `crop=edges`, Cloudinary `g_auto`, thumbor `smart`, …) crops on the detected faces when there are some and falls back to libvips attention otherwise; `false` always uses attention. Without the face model (slim) smart gravity is attention\n\nEnv: `XI_DETECT_SMART_FACES`",
          "title": "XI_DETECT_SMART_FACES",
          "type": "boolean"
        },
        "threads": {
          "default": 1,
          "description": "ONNX Runtime intra-op threads per inference; like XI_VIPS_CONCURRENCY, parallelism normally comes from concurrent requests (1–64)\n\nEnv: XI_DETECT_THREADS",
          "markdownDescription": "ONNX Runtime intra-op threads per inference; like `XI_VIPS_CONCURRENCY`, parallelism normally comes from concurrent requests (1–64)\n\nEnv: `XI_DETECT_THREADS`",
          "title": "XI_DETECT_THREADS",
          "type": "integer"
        },
        "verify_checksums": {
          "default": false,
          "description": "Check each model against the SHA256SUMS of its directory when it lists the model; a mismatch fails startup\n\nEnv: XI_DETECT_VERIFY_CHECKSUMS",
          "markdownDescription": "Check each model against the `SHA256SUMS` of its directory when it lists the model; a mismatch fails startup\n\nEnv: `XI_DETECT_VERIFY_CHECKSUMS`",
          "title": "XI_DETECT_VERIFY_CHECKSUMS",
          "type": "boolean"
        },
        "workers": {
          "default": 0,
          "description": "Inferences running at once (a semaphore; more wait); 0 is GOMAXPROCS / XI_DETECT_THREADS\n\nEnv: XI_DETECT_WORKERS",
          "markdownDescription": "Inferences running at once (a semaphore; more wait); `0` is GOMAXPROCS / `XI_DETECT_THREADS`\n\nEnv: `XI_DETECT_WORKERS`",
          "title": "XI_DETECT_WORKERS",
          "type": "integer"
        }
      },
      "type": "object"
    },
    "dev_mode": {
      "default": false,
      "description": "Allow any http(s) source (SSRF rules still apply), pretty errors, no signature requirement warnings\n\nEnv: XI_DEV_MODE",
      "markdownDescription": "Allow any http(s) source (SSRF rules still apply), pretty errors, no signature requirement warnings\n\nEnv: `XI_DEV_MODE`",
      "title": "XI_DEV_MODE",
      "type": "boolean"
    },
    "dialects": {
      "additionalProperties": false,
      "properties": {
        "awssih": {
          "additionalProperties": false,
          "properties": {
            "buckets": {
              "additionalProperties": {
                "type": "string"
              },
              "description": "bucket=source,…: SIH SOURCE_BUCKETS allowlist; a request without a bucket uses XI_DIALECTS_AWSSIH_SOURCE (URL-DIALECTS §13)\n\nEnv: XI_DIALECTS_AWSSIH_BUCKETS",
              "markdownDescription": "`bucket=source,…`: SIH `SOURCE_BUCKETS` allowlist; a request without a bucket uses `XI_DIALECTS_AWSSIH_SOURCE` (URL-DIALECTS §13)\n\nEnv: `XI_DIALECTS_AWSSIH_BUCKETS`",
              "title": "XI_DIALECTS_AWSSIH_BUCKETS",
              "type": "object"
            },
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_AWSSIH_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_AWSSIH_ENABLED`",
              "title": "XI_DIALECTS_AWSSIH_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_AWSSIH_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_AWSSIH_HOSTS`",
              "title": "XI_DIALECTS_AWSSIH_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_AWSSIH_KEYS (or XI_DIALECTS_AWSSIH_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_AWSSIH_KEYS` (or `XI_DIALECTS_AWSSIH_KEYS_FILE`)",
              "title": "XI_DIALECTS_AWSSIH_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_AWSSIH_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_AWSSIH_PREFIX`",
              "title": "XI_DIALECTS_AWSSIH_PREFIX",
              "type": "string"
            },
            "query_params": {
              "default": true,
              "description": "SIH query-param mapper (format, fit, width, height, rotate, flip, flop, grayscale)\n\nEnv: XI_DIALECTS_AWSSIH_QUERY_PARAMS",
              "markdownDescription": "SIH query-param mapper (`format`, `fit`, `width`, `height`, `rotate`, `flip`, `flop`, `grayscale`)\n\nEnv: `XI_DIALECTS_AWSSIH_QUERY_PARAMS`",
              "title": "XI_DIALECTS_AWSSIH_QUERY_PARAMS",
              "type": "boolean"
            },
            "rewrite_match_pattern": {
              "default": "",
              "description": "SIH REWRITE_MATCH_PATTERN: /regex/flags applied to non-JSON paths before forwarding\n\nEnv: XI_DIALECTS_AWSSIH_REWRITE_MATCH_PATTERN",
              "markdownDescription": "SIH `REWRITE_MATCH_PATTERN`: `/regex/flags` applied to non-JSON paths before forwarding\n\nEnv: `XI_DIALECTS_AWSSIH_REWRITE_MATCH_PATTERN`",
              "title": "XI_DIALECTS_AWSSIH_REWRITE_MATCH_PATTERN",
              "type": "string"
            },
            "rewrite_substitution": {
              "default": "",
              "description": "SIH REWRITE_SUBSTITUTION ($1, $<name>, $&, $$)\n\nEnv: XI_DIALECTS_AWSSIH_REWRITE_SUBSTITUTION",
              "markdownDescription": "SIH `REWRITE_SUBSTITUTION` (`$1`, `$<name>`, `$&`, `$$`)\n\nEnv: `XI_DIALECTS_AWSSIH_REWRITE_SUBSTITUTION`",
              "title": "XI_DIALECTS_AWSSIH_REWRITE_SUBSTITUTION",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_AWSSIH_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_AWSSIH_SOURCE`",
              "title": "XI_DIALECTS_AWSSIH_SOURCE",
              "type": "string"
            },
            "thumbor": {
              "default": false,
              "description": "Forward non-JSON paths to the thumbor dialect (SIH thumbor request type)\n\nEnv: XI_DIALECTS_AWSSIH_THUMBOR",
              "markdownDescription": "Forward non-JSON paths to the thumbor dialect (SIH thumbor request type)\n\nEnv: `XI_DIALECTS_AWSSIH_THUMBOR`",
              "title": "XI_DIALECTS_AWSSIH_THUMBOR",
              "type": "boolean"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_AWSSIH_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_AWSSIH_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_AWSSIH_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "bunny": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_BUNNY_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_BUNNY_ENABLED`",
              "title": "XI_DIALECTS_BUNNY_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_BUNNY_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_BUNNY_HOSTS`",
              "title": "XI_DIALECTS_BUNNY_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_BUNNY_KEYS (or XI_DIALECTS_BUNNY_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_BUNNY_KEYS` (or `XI_DIALECTS_BUNNY_KEYS_FILE`)",
              "title": "XI_DIALECTS_BUNNY_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_BUNNY_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_BUNNY_PREFIX`",
              "title": "XI_DIALECTS_BUNNY_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_BUNNY_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_BUNNY_SOURCE`",
              "title": "XI_DIALECTS_BUNNY_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_BUNNY_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_BUNNY_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_BUNNY_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "cloudflare": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_CLOUDFLARE_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_CLOUDFLARE_ENABLED`",
              "title": "XI_DIALECTS_CLOUDFLARE_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_CLOUDFLARE_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_CLOUDFLARE_HOSTS`",
              "title": "XI_DIALECTS_CLOUDFLARE_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_CLOUDFLARE_KEYS (or XI_DIALECTS_CLOUDFLARE_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_CLOUDFLARE_KEYS` (or `XI_DIALECTS_CLOUDFLARE_KEYS_FILE`)",
              "title": "XI_DIALECTS_CLOUDFLARE_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_CLOUDFLARE_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_CLOUDFLARE_PREFIX`",
              "title": "XI_DIALECTS_CLOUDFLARE_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_CLOUDFLARE_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_CLOUDFLARE_SOURCE`",
              "title": "XI_DIALECTS_CLOUDFLARE_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_CLOUDFLARE_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_CLOUDFLARE_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_CLOUDFLARE_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "cloudflare_images": {
          "additionalProperties": false,
          "properties": {
            "accounts": {
              "additionalProperties": {
                "type": "string"
              },
              "description": "account_hash=source,…: the source each Cloudflare Images account hash reads image ids from; empty = the mount's source for every hash, otherwise an unknown hash is 403. XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS are the raw Images signing keys (sig/exp); variants are presets\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_ACCOUNTS",
              "markdownDescription": "`account_hash=source,…`: the source each Cloudflare Images account hash reads image ids from; empty = the mount's source for every hash, otherwise an unknown hash is 403. `XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS` are the raw Images signing keys (`sig`/`exp`); variants are presets\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_ACCOUNTS`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_ACCOUNTS",
              "type": "object"
            },
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_ENABLED`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_HOSTS`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS (or XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS` (or `XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS_FILE`)",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_PREFIX`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_SOURCE`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_CLOUDFLARE_IMAGES_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_CLOUDFLARE_IMAGES_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_CLOUDFLARE_IMAGES_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "cloudinary": {
          "additionalProperties": false,
          "properties": {
            "clouds": {
              "additionalProperties": {
                "type": "string"
              },
              "description": "cloud=source,…\n\nEnv: XI_DIALECTS_CLOUDINARY_CLOUDS",
              "markdownDescription": "`cloud=source,…`\n\nEnv: `XI_DIALECTS_CLOUDINARY_CLOUDS`",
              "title": "XI_DIALECTS_CLOUDINARY_CLOUDS",
              "type": "object"
            },
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_CLOUDINARY_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_CLOUDINARY_ENABLED`",
              "title": "XI_DIALECTS_CLOUDINARY_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_CLOUDINARY_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_CLOUDINARY_HOSTS`",
              "title": "XI_DIALECTS_CLOUDINARY_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_CLOUDINARY_KEYS (or XI_DIALECTS_CLOUDINARY_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_CLOUDINARY_KEYS` (or `XI_DIALECTS_CLOUDINARY_KEYS_FILE`)",
              "title": "XI_DIALECTS_CLOUDINARY_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_CLOUDINARY_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_CLOUDINARY_PREFIX`",
              "title": "XI_DIALECTS_CLOUDINARY_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_CLOUDINARY_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_CLOUDINARY_SOURCE`",
              "title": "XI_DIALECTS_CLOUDINARY_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_CLOUDINARY_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_CLOUDINARY_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_CLOUDINARY_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "default": {
          "default": "imgproxy",
          "description": "Fallback dialect for paths no mount matches (it must be enabled); a dialect other than imgproxy serves them with its primary mount's source and unsigned policy\n\nEnv: XI_DIALECTS_DEFAULT",
          "enum": [
            "imgproxy",
            "query",
            "wsrv",
            "cloudflare",
            "cloudflare_images",
            "imgix",
            "cloudinary",
            "imagekit",
            "bunny",
            "thumbor",
            "awssih"
          ],
          "markdownDescription": "Fallback dialect for paths no mount matches (it must be enabled); a dialect other than imgproxy serves them with its primary mount's source and unsigned policy\n\nEnv: `XI_DIALECTS_DEFAULT`",
          "title": "XI_DIALECTS_DEFAULT",
          "type": "string"
        },
        "imagekit": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_IMAGEKIT_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_IMAGEKIT_ENABLED`",
              "title": "XI_DIALECTS_IMAGEKIT_ENABLED",
              "type": "boolean"
            },
            "endpoint_path": {
              "default": "",
              "description": "ImageKit URL-endpoint id kept in the path of host-bound mounts (https://ik.imagekit.io/<id>/… → /<id>), for every ImageKit mount: stripped before parsing, excluded from the source key and the signed message; requests outside it are 400 (URL-DIALECTS §7)\n\nEnv: XI_DIALECTS_IMAGEKIT_ENDPOINT_PATH",
              "markdownDescription": "ImageKit URL-endpoint id kept in the path of host-bound mounts (`https://ik.imagekit.io/<id>/…` → `/<id>`), for every ImageKit mount: stripped before parsing, excluded from the source key and the signed message; requests outside it are 400 (URL-DIALECTS §7)\n\nEnv: `XI_DIALECTS_IMAGEKIT_ENDPOINT_PATH`",
              "title": "XI_DIALECTS_IMAGEKIT_ENDPOINT_PATH",
              "type": "string"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_IMAGEKIT_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_IMAGEKIT_HOSTS`",
              "title": "XI_DIALECTS_IMAGEKIT_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_IMAGEKIT_KEYS (or XI_DIALECTS_IMAGEKIT_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_IMAGEKIT_KEYS` (or `XI_DIALECTS_IMAGEKIT_KEYS_FILE`)",
              "title": "XI_DIALECTS_IMAGEKIT_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_IMAGEKIT_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_IMAGEKIT_PREFIX`",
              "title": "XI_DIALECTS_IMAGEKIT_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_IMAGEKIT_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_IMAGEKIT_SOURCE`",
              "title": "XI_DIALECTS_IMAGEKIT_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_IMAGEKIT_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_IMAGEKIT_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_IMAGEKIT_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "imgix": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_IMGIX_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_IMGIX_ENABLED`",
              "title": "XI_DIALECTS_IMGIX_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_IMGIX_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_IMGIX_HOSTS`",
              "title": "XI_DIALECTS_IMGIX_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "imgix secure URL tokens, used verbatim (,-separated for rotation: any verifies, the first signs in the playground). With tokens, s = hex(MD5(token ‖ path ‖ ? ‖ query without s)) is verified and expires enforced; without, s is not checked and every URL follows the mount's unsigned policy. XI_SIGNING_REQUIRED=true rejects unsigned imgix URLs on mounts with tokens. Prefix default /imgix/; host-bind (XI_DIALECTS_IMGIX_HOSTS) for unpic's imgix provider; XI_DIALECTS_IMGIX_SOURCE resolves object keys (URL-DIALECTS §5)\n\nEnv: XI_DIALECTS_IMGIX_KEYS (or XI_DIALECTS_IMGIX_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "imgix secure URL tokens, used verbatim (`,`-separated for rotation: any verifies, the first signs in the playground). With tokens, `s` = hex(MD5(token ‖ path ‖ `?` ‖ query without `s`)) is verified and `expires` enforced; without, `s` is not checked and every URL follows the mount's unsigned policy. `XI_SIGNING_REQUIRED=true` rejects unsigned imgix URLs on mounts with tokens. Prefix default `/imgix/`; host-bind (`XI_DIALECTS_IMGIX_HOSTS`) for unpic's imgix provider; `XI_DIALECTS_IMGIX_SOURCE` resolves object keys (URL-DIALECTS §5)\n\nEnv: `XI_DIALECTS_IMGIX_KEYS` (or `XI_DIALECTS_IMGIX_KEYS_FILE`)",
              "title": "XI_DIALECTS_IMGIX_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_IMGIX_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_IMGIX_PREFIX`",
              "title": "XI_DIALECTS_IMGIX_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_IMGIX_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_IMGIX_SOURCE`",
              "title": "XI_DIALECTS_IMGIX_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_IMGIX_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_IMGIX_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_IMGIX_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "imgproxy": {
          "additionalProperties": false,
          "properties": {
            "arguments_separator": {
              "default": ":",
              "description": "Env: XI_DIALECTS_IMGPROXY_ARGUMENTS_SEPARATOR",
              "markdownDescription": "Env: `XI_DIALECTS_IMGPROXY_ARGUMENTS_SEPARATOR`",
              "title": "XI_DIALECTS_IMGPROXY_ARGUMENTS_SEPARATOR",
              "type": "string"
            },
            "base_url": {
              "default": "",
              "description": "Env: XI_DIALECTS_IMGPROXY_BASE_URL",
              "markdownDescription": "Env: `XI_DIALECTS_IMGPROXY_BASE_URL`",
              "title": "XI_DIALECTS_IMGPROXY_BASE_URL",
              "type": "string"
            },
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_IMGPROXY_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_IMGPROXY_ENABLED`",
              "title": "XI_DIALECTS_IMGPROXY_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_IMGPROXY_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_IMGPROXY_HOSTS`",
              "title": "XI_DIALECTS_IMGPROXY_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_IMGPROXY_KEYS (or XI_DIALECTS_IMGPROXY_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_IMGPROXY_KEYS` (or `XI_DIALECTS_IMGPROXY_KEYS_FILE`)",
              "title": "XI_DIALECTS_IMGPROXY_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_IMGPROXY_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_IMGPROXY_PREFIX`",
              "title": "XI_DIALECTS_IMGPROXY_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_IMGPROXY_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_IMGPROXY_SOURCE`",
              "title": "XI_DIALECTS_IMGPROXY_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_IMGPROXY_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_IMGPROXY_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_IMGPROXY_UNSIGNED_POLICY",
              "type": "string"
            },
            "url_replacements": {
              "description": "regex=replacement;… (;-separated in env)\n\nEnv: XI_DIALECTS_IMGPROXY_URL_REPLACEMENTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "`regex=replacement;…` (`;`-separated in env)\n\nEnv: `XI_DIALECTS_IMGPROXY_URL_REPLACEMENTS`",
              "title": "XI_DIALECTS_IMGPROXY_URL_REPLACEMENTS",
              "type": "array"
            }
          },
          "type": "object"
        },
        "query": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_QUERY_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_QUERY_ENABLED`",
              "title": "XI_DIALECTS_QUERY_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_QUERY_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_QUERY_HOSTS`",
              "title": "XI_DIALECTS_QUERY_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_QUERY_KEYS (or XI_DIALECTS_QUERY_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_QUERY_KEYS` (or `XI_DIALECTS_QUERY_KEYS_FILE`)",
              "title": "XI_DIALECTS_QUERY_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_QUERY_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_QUERY_PREFIX`",
              "title": "XI_DIALECTS_QUERY_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_QUERY_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_QUERY_SOURCE`",
              "title": "XI_DIALECTS_QUERY_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_QUERY_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_QUERY_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_QUERY_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "simple": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "unpic providers of the table-driven family, e.g. nextjs,ipx,contentful (names in URL-DIALECTS §12; an unknown name fails startup). Each is mounted at its native prefixes and /simple/<name>/; no _PREFIX/_KEYS (xi_sig uses XI_SIGNING_*)\n\nEnv: XI_DIALECTS_SIMPLE_ENABLED",
              "items": {
                "type": "string"
              },
              "markdownDescription": "unpic providers of the table-driven family, e.g. `nextjs,ipx,contentful` (names in URL-DIALECTS §12; an unknown name fails startup). Each is mounted at its native prefixes and `/simple/<name>/`; no `_PREFIX`/`_KEYS` (xi_sig uses `XI_SIGNING_*`)\n\nEnv: `XI_DIALECTS_SIMPLE_ENABLED`",
              "title": "XI_DIALECTS_SIMPLE_ENABLED",
              "type": "array"
            },
            "hosts": {
              "description": "Hosts bound to the whole family; the provider is picked per request (native prefix, upstream domain, then the first enabled CDN-style provider)\n\nEnv: XI_DIALECTS_SIMPLE_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Hosts bound to the whole family; the provider is picked per request (native prefix, upstream domain, then the first enabled CDN-style provider)\n\nEnv: `XI_DIALECTS_SIMPLE_HOSTS`",
              "title": "XI_DIALECTS_SIMPLE_HOSTS",
              "type": "array"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_SIMPLE_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_SIMPLE_SOURCE`",
              "title": "XI_DIALECTS_SIMPLE_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_SIMPLE_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_SIMPLE_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_SIMPLE_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "strict": {
          "default": false,
          "description": "400 on unknown/unsupported options\n\nEnv: XI_DIALECTS_STRICT",
          "markdownDescription": "400 on unknown/unsupported options\n\nEnv: `XI_DIALECTS_STRICT`",
          "title": "XI_DIALECTS_STRICT",
          "type": "boolean"
        },
        "thumbor": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_THUMBOR_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_THUMBOR_ENABLED`",
              "title": "XI_DIALECTS_THUMBOR_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_THUMBOR_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_THUMBOR_HOSTS`",
              "title": "XI_DIALECTS_THUMBOR_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_THUMBOR_KEYS (or XI_DIALECTS_THUMBOR_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_THUMBOR_KEYS` (or `XI_DIALECTS_THUMBOR_KEYS_FILE`)",
              "title": "XI_DIALECTS_THUMBOR_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_THUMBOR_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_THUMBOR_PREFIX`",
              "title": "XI_DIALECTS_THUMBOR_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_THUMBOR_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_THUMBOR_SOURCE`",
              "title": "XI_DIALECTS_THUMBOR_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_THUMBOR_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_THUMBOR_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_THUMBOR_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        },
        "wsrv": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "description": "<NAME>: IMGPROXY, QUERY, WSRV, CLOUDFLARE, CLOUDFLARE_IMAGES, IMGIX, CLOUDINARY, IMAGEKIT, BUNNY, THUMBOR, AWSSIH, SIMPLE (for SIMPLE a list of providers: nextjs,netlify,ipx). Extra mounts of one dialect are YAML-only: dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]\n\nDefault: imgproxy true, others false\n\nEnv: XI_DIALECTS_WSRV_ENABLED",
              "markdownDescription": "`<NAME>`: `IMGPROXY`, `QUERY`, `WSRV`, `CLOUDFLARE`, `CLOUDFLARE_IMAGES`, `IMGIX`, `CLOUDINARY`, `IMAGEKIT`, `BUNNY`, `THUMBOR`, `AWSSIH`, `SIMPLE` (for `SIMPLE` a list of providers: `nextjs,netlify,ipx`). Extra mounts of one dialect are YAML-only: `dialects.<name>.mounts: [{prefix, hosts, source, keys, unsigned_policy}]`\n\nDefault: imgproxy `true`, others `false`\n\nEnv: `XI_DIALECTS_WSRV_ENABLED`",
              "title": "XI_DIALECTS_WSRV_ENABLED",
              "type": "boolean"
            },
            "hosts": {
              "description": "Host bindings\n\nEnv: XI_DIALECTS_WSRV_HOSTS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Host bindings\n\nEnv: `XI_DIALECTS_WSRV_HOSTS`",
              "title": "XI_DIALECTS_WSRV_HOSTS",
              "type": "array"
            },
            "keys": {
              "description": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: XI_DIALECTS_WSRV_KEYS (or XI_DIALECTS_WSRV_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/secret"
              },
              "markdownDescription": "Dialect secrets (imgix tokens, thumbor key, cloudinary api secret, imagekit private key, bunny token key, AWS SIH secret used verbatim)\n\nEnv: `XI_DIALECTS_WSRV_KEYS` (or `XI_DIALECTS_WSRV_KEYS_FILE`)",
              "title": "XI_DIALECTS_WSRV_KEYS",
              "type": "array"
            },
            "mounts": {
              "description": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "items": {
                "$ref": "#/$defs/mount"
              },
              "markdownDescription": "Extra mounts of this dialect, each with its own prefix or hosts (file only; no environment variable).",
              "type": "array"
            },
            "prefix": {
              "description": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: XI_DIALECTS_WSRV_PREFIX",
              "markdownDescription": "Mount prefix\n\nDefault: see URL-DIALECTS.md\n\nEnv: `XI_DIALECTS_WSRV_PREFIX`",
              "title": "XI_DIALECTS_WSRV_PREFIX",
              "type": "string"
            },
            "source": {
              "default": "",
              "description": "Bound source for relative paths\n\nEnv: XI_DIALECTS_WSRV_SOURCE",
              "markdownDescription": "Bound source for relative paths\n\nEnv: `XI_DIALECTS_WSRV_SOURCE`",
              "title": "XI_DIALECTS_WSRV_SOURCE",
              "type": "string"
            },
            "unsigned_policy": {
              "description": "Per-dialect override\n\nDefault: global\n\nEnv: XI_DIALECTS_WSRV_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Per-dialect override\n\nDefault: global\n\nEnv: `XI_DIALECTS_WSRV_UNSIGNED_POLICY`",
              "title": "XI_DIALECTS_WSRV_UNSIGNED_POLICY",
              "type": "string"
            }
          },
          "type": "object"
        }
      },
      "type": "object"
    },
    "encryption": {
      "additionalProperties": false,
      "properties": {
        "key": {
          "$ref": "#/$defs/hexKey",
          "description": "Hex AES keys (16/24/32 bytes) for enc/ sources and pdf_password tokens ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); the first encrypts, all decrypt (rotation). XI_ENCRYPTION_KEY is the deprecated single-key alias (YAML encryption.key); setting both is an error\n\nEnv: XI_ENCRYPTION_KEY (or XI_ENCRYPTION_KEY_FILE)",
          "markdownDescription": "Hex AES keys (16/24/32 bytes) for `enc/` sources and `pdf_password` tokens ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); the first encrypts, all decrypt (rotation). `XI_ENCRYPTION_KEY` is the deprecated single-key alias (YAML `encryption.key`); setting both is an error\n\nEnv: `XI_ENCRYPTION_KEY` (or `XI_ENCRYPTION_KEY_FILE`)",
          "title": "XI_ENCRYPTION_KEY"
        },
        "keys": {
          "description": "Hex AES keys (16/24/32 bytes) for enc/ sources and pdf_password tokens ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); the first encrypts, all decrypt (rotation). XI_ENCRYPTION_KEY is the deprecated single-key alias (YAML encryption.key); setting both is an error\n\nEnv: XI_ENCRYPTION_KEYS (or XI_ENCRYPTION_KEYS_FILE)",
          "items": {
            "$ref": "#/$defs/hexKey"
          },
          "markdownDescription": "Hex AES keys (16/24/32 bytes) for `enc/` sources and `pdf_password` tokens ([URL-DIALECTS §3.7](URL-DIALECTS.md#37-password-protected-pdfs-wp-115)); the first encrypts, all decrypt (rotation). `XI_ENCRYPTION_KEY` is the deprecated single-key alias (YAML `encryption.key`); setting both is an error\n\nEnv: `XI_ENCRYPTION_KEYS` (or `XI_ENCRYPTION_KEYS_FILE`)",
          "title": "XI_ENCRYPTION_KEYS",
          "type": "array"
        }
      },
      "type": "object"
    },
    "fallback": {
      "additionalProperties": false,
      "properties": {
        "allow_request_url": {
          "default": false,
          "description": "Honour a request's own fallback image (imgproxy fallback_image_url); it must still pass the source allowlist, otherwise XI_FALLBACK_SOURCE is used\n\nEnv: XI_FALLBACK_ALLOW_REQUEST_URL",
          "markdownDescription": "Honour a request's own fallback image (imgproxy `fallback_image_url`); it must still pass the source allowlist, otherwise `XI_FALLBACK_SOURCE` is used\n\nEnv: `XI_FALLBACK_ALLOW_REQUEST_URL`",
          "title": "XI_FALLBACK_ALLOW_REQUEST_URL",
          "type": "boolean"
        },
        "source": {
          "default": "",
          "description": "Fallback image (source string)\n\nEnv: XI_FALLBACK_SOURCE",
          "markdownDescription": "Fallback image (source string)\n\nEnv: `XI_FALLBACK_SOURCE`",
          "title": "XI_FALLBACK_SOURCE",
          "type": "string"
        },
        "status": {
          "default": 200,
          "description": "Status used with fallback\n\nEnv: XI_FALLBACK_STATUS",
          "markdownDescription": "Status used with fallback\n\nEnv: `XI_FALLBACK_STATUS`",
          "title": "XI_FALLBACK_STATUS",
          "type": "integer"
        },
        "ttl": {
          "$ref": "#/$defs/duration",
          "default": "1m0s",
          "description": "Cache-Control: public, max-age of a fallback response; 0 sends no-store\n\nEnv: XI_FALLBACK_TTL",
          "markdownDescription": "`Cache-Control: public, max-age` of a fallback response; `0` sends `no-store`\n\nEnv: `XI_FALLBACK_TTL`",
          "title": "XI_FALLBACK_TTL"
        }
      },
      "type": "object"
    },
    "fetch": {
      "additionalProperties": false,
      "properties": {
        "allow_private_networks": {
          "default": false,
          "description": "Explicit opt-in to every private range\n\nEnv: XI_FETCH_ALLOW_PRIVATE_NETWORKS",
          "markdownDescription": "Explicit opt-in to every private range\n\nEnv: `XI_FETCH_ALLOW_PRIVATE_NETWORKS`",
          "title": "XI_FETCH_ALLOW_PRIVATE_NETWORKS",
          "type": "boolean"
        },
        "allowed_networks": {
          "description": "CIDRs exempt from the private-address block without opening every range (e.g. 10.20.0.0/16 for an in-cluster object store)\n\nEnv: XI_FETCH_ALLOWED_NETWORKS",
          "items": {
            "$ref": "#/$defs/cidr"
          },
          "markdownDescription": "CIDRs exempt from the private-address block without opening every range (e.g. `10.20.0.0/16` for an in-cluster object store)\n\nEnv: `XI_FETCH_ALLOWED_NETWORKS`",
          "title": "XI_FETCH_ALLOWED_NETWORKS",
          "type": "array"
        },
        "allowed_ports": {
          "default": [
            80,
            443
          ],
          "description": "SSRF port allowlist\n\nEnv: XI_FETCH_ALLOWED_PORTS",
          "items": {
            "type": "integer"
          },
          "markdownDescription": "SSRF port allowlist\n\nEnv: `XI_FETCH_ALLOWED_PORTS`",
          "title": "XI_FETCH_ALLOWED_PORTS",
          "type": "array"
        },
        "max_concurrent": {
          "default": 256,
          "description": "Origin downloads in progress at once, all sources together. Coalesced requests and cache hits take none; a download waits for a slot within its XI_FETCH_TIMEOUT (the wait counts against it) and is 503 when none frees up\n\nEnv: XI_FETCH_MAX_CONCURRENT",
          "markdownDescription": "Origin downloads in progress at once, all sources together. Coalesced requests and cache hits take none; a download waits for a slot within its `XI_FETCH_TIMEOUT` (the wait counts against it) and is 503 when none frees up\n\nEnv: `XI_FETCH_MAX_CONCURRENT`",
          "title": "XI_FETCH_MAX_CONCURRENT",
          "type": "integer"
        },
        "max_redirects": {
          "default": 5,
          "description": "Env: XI_FETCH_MAX_REDIRECTS",
          "markdownDescription": "Env: `XI_FETCH_MAX_REDIRECTS`",
          "title": "XI_FETCH_MAX_REDIRECTS",
          "type": "integer"
        },
        "retry": {
          "default": true,
          "description": "One retry, after a jittered ~100 ms backoff, of a transient failure (origin 5xx, connection error, attempt timeout); never for 4xx\n\nEnv: XI_FETCH_RETRY",
          "markdownDescription": "One retry, after a jittered ~100 ms backoff, of a transient failure (origin 5xx, connection error, attempt timeout); never for 4xx\n\nEnv: `XI_FETCH_RETRY`",
          "title": "XI_FETCH_RETRY",
          "type": "boolean"
        },
        "timeout": {
          "$ref": "#/$defs/duration",
          "default": "10s",
          "description": "Per download attempt\n\nEnv: XI_FETCH_TIMEOUT",
          "markdownDescription": "Per download attempt\n\nEnv: `XI_FETCH_TIMEOUT`",
          "title": "XI_FETCH_TIMEOUT"
        },
        "user_agent": {
          "default": "",
          "description": "Env: XI_FETCH_USER_AGENT",
          "markdownDescription": "Env: `XI_FETCH_USER_AGENT`",
          "title": "XI_FETCH_USER_AGENT",
          "type": "string"
        }
      },
      "type": "object"
    },
    "formats": {
      "additionalProperties": false,
      "properties": {
        "auto": {
          "additionalProperties": false,
          "properties": {
            "avif": {
              "default": true,
              "description": "Accept negotiation\n\nEnv: XI_FORMATS_AUTO_AVIF",
              "markdownDescription": "Accept negotiation\n\nEnv: `XI_FORMATS_AUTO_AVIF`",
              "title": "XI_FORMATS_AUTO_AVIF",
              "type": "boolean"
            },
            "jxl": {
              "default": false,
              "description": "Accept negotiation\n\nEnv: XI_FORMATS_AUTO_JXL",
              "markdownDescription": "Accept negotiation\n\nEnv: `XI_FORMATS_AUTO_JXL`",
              "title": "XI_FORMATS_AUTO_JXL",
              "type": "boolean"
            },
            "webp": {
              "default": true,
              "description": "Accept negotiation\n\nEnv: XI_FORMATS_AUTO_WEBP",
              "markdownDescription": "Accept negotiation\n\nEnv: `XI_FORMATS_AUTO_WEBP`",
              "title": "XI_FORMATS_AUTO_WEBP",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "autoquality": {
          "additionalProperties": false,
          "properties": {
            "allowed_error": {
              "default": 0.001,
              "description": "Target: DSSIM (dssim) or bytes (size, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: XI_FORMATS_AUTOQUALITY_ALLOWED_ERROR",
              "markdownDescription": "Target: DSSIM (`dssim`) or bytes (`size`, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: `XI_FORMATS_AUTOQUALITY_ALLOWED_ERROR`",
              "title": "XI_FORMATS_AUTOQUALITY_ALLOWED_ERROR",
              "type": "number"
            },
            "format_max": {
              "additionalProperties": false,
              "default": {
                "avif": 80,
                "jpeg": 90,
                "webp": 90
              },
              "description": "Per-format maximum quality (IMGPROXY_AUTOQUALITY_FORMAT_MAX); as above\n\nEnv: XI_FORMATS_AUTOQUALITY_FORMAT_MAX",
              "markdownDescription": "Per-format maximum quality (`IMGPROXY_AUTOQUALITY_FORMAT_MAX`); as above\n\nEnv: `XI_FORMATS_AUTOQUALITY_FORMAT_MAX`",
              "properties": {
                "avif": {
                  "type": "integer"
                },
                "heic": {
                  "type": "integer"
                },
                "jpeg": {
                  "type": "integer"
                },
                "jpg": {
                  "type": "integer"
                },
                "jxl": {
                  "type": "integer"
                },
                "webp": {
                  "type": "integer"
                }
              },
              "title": "XI_FORMATS_AUTOQUALITY_FORMAT_MAX",
              "type": "object"
            },
            "format_min": {
              "additionalProperties": false,
              "default": {
                "avif": 30,
                "jpeg": 40,
                "webp": 40
              },
              "description": "Per-format minimum quality (imgproxy IMGPROXY_AUTOQUALITY_FORMAT_MIN); formats not listed use _MIN. Setting the variable replaces the whole default map\n\nEnv: XI_FORMATS_AUTOQUALITY_FORMAT_MIN",
              "markdownDescription": "Per-format minimum quality (imgproxy `IMGPROXY_AUTOQUALITY_FORMAT_MIN`); formats not listed use `_MIN`. Setting the variable replaces the whole default map\n\nEnv: `XI_FORMATS_AUTOQUALITY_FORMAT_MIN`",
              "properties": {
                "avif": {
                  "type": "integer"
                },
                "heic": {
                  "type": "integer"
                },
                "jpeg": {
                  "type": "integer"
                },
                "jpg": {
                  "type": "integer"
                },
                "jxl": {
                  "type": "integer"
                },
                "webp": {
                  "type": "integer"
                }
              },
              "title": "XI_FORMATS_AUTOQUALITY_FORMAT_MIN",
              "type": "object"
            },
            "format_target": {
              "additionalProperties": false,
              "description": "Per-format target, e.g. avif=0.015; formats not listed use _TARGET\n\nEnv: XI_FORMATS_AUTOQUALITY_FORMAT_TARGET",
              "markdownDescription": "Per-format target, e.g. `avif=0.015`; formats not listed use `_TARGET`\n\nEnv: `XI_FORMATS_AUTOQUALITY_FORMAT_TARGET`",
              "properties": {
                "avif": {
                  "type": "number"
                },
                "heic": {
                  "type": "number"
                },
                "jpeg": {
                  "type": "number"
                },
                "jpg": {
                  "type": "number"
                },
                "jxl": {
                  "type": "number"
                },
                "webp": {
                  "type": "number"
                }
              },
              "title": "XI_FORMATS_AUTOQUALITY_FORMAT_TARGET",
              "type": "object"
            },
            "max": {
              "default": 85,
              "description": "Target: DSSIM (dssim) or bytes (size, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: XI_FORMATS_AUTOQUALITY_MAX",
              "markdownDescription": "Target: DSSIM (`dssim`) or bytes (`size`, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: `XI_FORMATS_AUTOQUALITY_MAX`",
              "title": "XI_FORMATS_AUTOQUALITY_MAX",
              "type": "integer"
            },
            "method": {
              "default": "none",
              "description": "none | size | dssim (imgproxy IMGPROXY_AUTOQUALITY_METHOD; ml is not implemented). A request's autoquality option overrides it; with the method from config, an explicit request quality wins. See FORMATS.md §6.4\n\nEnv: XI_FORMATS_AUTOQUALITY_METHOD",
              "enum": [
                "none",
                "size",
                "dssim"
              ],
              "markdownDescription": "`none` | `size` | `dssim` (imgproxy `IMGPROXY_AUTOQUALITY_METHOD`; `ml` is not implemented). A request's `autoquality` option overrides it; with the method from config, an explicit request quality wins. See FORMATS.md §6.4\n\nEnv: `XI_FORMATS_AUTOQUALITY_METHOD`",
              "title": "XI_FORMATS_AUTOQUALITY_METHOD",
              "type": "string"
            },
            "min": {
              "default": 60,
              "description": "Target: DSSIM (dssim) or bytes (size, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: XI_FORMATS_AUTOQUALITY_MIN",
              "markdownDescription": "Target: DSSIM (`dssim`) or bytes (`size`, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: `XI_FORMATS_AUTOQUALITY_MIN`",
              "title": "XI_FORMATS_AUTOQUALITY_MIN",
              "type": "integer"
            },
            "target": {
              "default": 0.02,
              "description": "Target: DSSIM (dssim) or bytes (size, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: XI_FORMATS_AUTOQUALITY_TARGET",
              "markdownDescription": "Target: DSSIM (`dssim`) or bytes (`size`, must then be ≥ 1); quality range searched; how far from the DSSIM target a save may land and stop the search (0–1)\n\nEnv: `XI_FORMATS_AUTOQUALITY_TARGET`",
              "title": "XI_FORMATS_AUTOQUALITY_TARGET",
              "type": "number"
            }
          },
          "type": "object"
        },
        "avif": {
          "additionalProperties": false,
          "properties": {
            "effort": {
              "description": "<FMT>: WEBP (0–6), AVIF (0–9), JXL (1–10), GIF (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: XI_FORMATS_AVIF_EFFORT",
              "markdownDescription": "`<FMT>`: `WEBP` (0–6), `AVIF` (0–9), `JXL` (1–10), `GIF` (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: `XI_FORMATS_AVIF_EFFORT`",
              "title": "XI_FORMATS_AVIF_EFFORT",
              "type": "integer"
            },
            "max_pixels": {
              "default": 16000000,
              "description": "Env: XI_FORMATS_AVIF_MAX_PIXELS",
              "markdownDescription": "Env: `XI_FORMATS_AVIF_MAX_PIXELS`",
              "title": "XI_FORMATS_AVIF_MAX_PIXELS",
              "type": "integer"
            },
            "quality": {
              "description": "<FMT>: JPEG, WEBP, AVIF, HEIC, JXL; other formats use XI_FORMATS_QUALITY\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: XI_FORMATS_AVIF_QUALITY",
              "markdownDescription": "`<FMT>`: `JPEG`, `WEBP`, `AVIF`, `HEIC`, `JXL`; other formats use `XI_FORMATS_QUALITY`\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: `XI_FORMATS_AVIF_QUALITY`",
              "title": "XI_FORMATS_AVIF_QUALITY",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "best": {
          "additionalProperties": false,
          "properties": {
            "complexity_threshold": {
              "default": 5.5,
              "description": "Content score below which best tries lossless formats only, above which lossy only; within ±0.75 of it both. 0 skips the analysis (every image is tried as a photo). Photos score ≈ 7–8, flat graphics and text ≈ 1–4.5 (FORMATS.md §6.3)\n\nEnv: XI_FORMATS_BEST_COMPLEXITY_THRESHOLD",
              "markdownDescription": "Content score below which `best` tries lossless formats only, above which lossy only; within ±0.75 of it both. `0` skips the analysis (every image is tried as a photo). Photos score ≈ 7–8, flat graphics and text ≈ 1–4.5 (FORMATS.md §6.3)\n\nEnv: `XI_FORMATS_BEST_COMPLEXITY_THRESHOLD`",
              "title": "XI_FORMATS_BEST_COMPLEXITY_THRESHOLD",
              "type": "number"
            },
            "max_pixels": {
              "default": 8000000,
              "description": "format=best (imgproxy f:best, query f=best) is skipped above this many output pixels (all frames): the negotiated format is encoded once instead (FORMATS.md §6.3)\n\nEnv: XI_FORMATS_BEST_MAX_PIXELS",
              "markdownDescription": "`format=best` (imgproxy `f:best`, query `f=best`) is skipped above this many output pixels (all frames): the negotiated format is encoded once instead (FORMATS.md §6.3)\n\nEnv: `XI_FORMATS_BEST_MAX_PIXELS`",
              "title": "XI_FORMATS_BEST_MAX_PIXELS",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "enforce": {
          "additionalProperties": false,
          "properties": {
            "avif": {
              "default": false,
              "description": "Env: XI_FORMATS_ENFORCE_AVIF",
              "markdownDescription": "Env: `XI_FORMATS_ENFORCE_AVIF`",
              "title": "XI_FORMATS_ENFORCE_AVIF",
              "type": "boolean"
            },
            "jxl": {
              "default": false,
              "description": "Env: XI_FORMATS_ENFORCE_JXL",
              "markdownDescription": "Env: `XI_FORMATS_ENFORCE_JXL`",
              "title": "XI_FORMATS_ENFORCE_JXL",
              "type": "boolean"
            },
            "webp": {
              "default": false,
              "description": "Env: XI_FORMATS_ENFORCE_WEBP",
              "markdownDescription": "Env: `XI_FORMATS_ENFORCE_WEBP`",
              "title": "XI_FORMATS_ENFORCE_WEBP",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "eps": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "default": false,
              "description": "**Security-sensitive; gpl image only.** Render EPS, PostScript and PostScript-based Illustrator sources with Ghostscript (AGPL-3.0), a full PostScript interpreter run on untrusted input in a sandboxed subprocess per request (ADR 0011, FORMATS §1.6). true without a usable gs stops startup. Off, a DOS EPS file's embedded TIFF preview is used when it has one, and any other EPS answers 422 \"EPS needs the gpl image with XI_FORMATS_EPS_ENABLED=true\". Enabling it makes the running image AGPL-3.0 software you offer over a network (see the security note below the table)\n\nEnv: XI_FORMATS_EPS_ENABLED",
              "markdownDescription": "**Security-sensitive; `gpl` image only.** Render EPS, PostScript and PostScript-based Illustrator sources with Ghostscript (AGPL-3.0), a full PostScript interpreter run on untrusted input in a sandboxed subprocess per request (ADR 0011, FORMATS §1.6). `true` without a usable `gs` stops startup. Off, a DOS EPS file's embedded TIFF preview is used when it has one, and any other EPS answers 422 \"EPS needs the gpl image with XI_FORMATS_EPS_ENABLED=true\". Enabling it makes the running image AGPL-3.0 software you offer over a network (see the security note below the table)\n\nEnv: `XI_FORMATS_EPS_ENABLED`",
              "title": "XI_FORMATS_EPS_ENABLED",
              "type": "boolean"
            },
            "gs_path": {
              "default": "gs",
              "description": "Ghostscript executable: absolute path or a name looked up in PATH (the gpl image has /opt/xtandard/bin/gs)\n\nEnv: XI_FORMATS_EPS_GS_PATH",
              "markdownDescription": "Ghostscript executable: absolute path or a name looked up in `PATH` (the gpl image has `/opt/xtandard/bin/gs`)\n\nEnv: `XI_FORMATS_EPS_GS_PATH`",
              "title": "XI_FORMATS_EPS_GS_PATH",
              "type": "string"
            },
            "max_dpi": {
              "default": 600,
              "description": "Highest render resolution (1–2400); pages are also capped at XI_LIMITS_MAX_SRC_PIXELS by lowering the resolution\n\nEnv: XI_FORMATS_EPS_MAX_DPI",
              "markdownDescription": "Highest render resolution (1–2400); pages are also capped at `XI_LIMITS_MAX_SRC_PIXELS` by lowering the resolution\n\nEnv: `XI_FORMATS_EPS_MAX_DPI`",
              "title": "XI_FORMATS_EPS_MAX_DPI",
              "type": "number"
            },
            "max_procs": {
              "default": 2,
              "description": "Concurrent Ghostscript processes (1–256), on top of the worker limit; a render beyond it fails fast with 429\n\nEnv: XI_FORMATS_EPS_MAX_PROCS",
              "markdownDescription": "Concurrent Ghostscript processes (1–256), on top of the worker limit; a render beyond it fails fast with 429\n\nEnv: `XI_FORMATS_EPS_MAX_PROCS`",
              "title": "XI_FORMATS_EPS_MAX_PROCS",
              "type": "integer"
            },
            "timeout": {
              "$ref": "#/$defs/duration",
              "default": "10s",
              "description": "Wall-clock limit of one render (100ms–10m); the process is killed with its process group and the request answers 504. Its CPU-time limit is the same, rounded up, plus one second\n\nEnv: XI_FORMATS_EPS_TIMEOUT",
              "markdownDescription": "Wall-clock limit of one render (100ms–10m); the process is killed with its process group and the request answers 504. Its CPU-time limit is the same, rounded up, plus one second\n\nEnv: `XI_FORMATS_EPS_TIMEOUT`",
              "title": "XI_FORMATS_EPS_TIMEOUT"
            }
          },
          "type": "object"
        },
        "gif": {
          "additionalProperties": false,
          "properties": {
            "effort": {
              "description": "<FMT>: WEBP (0–6), AVIF (0–9), JXL (1–10), GIF (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: XI_FORMATS_GIF_EFFORT",
              "markdownDescription": "`<FMT>`: `WEBP` (0–6), `AVIF` (0–9), `JXL` (1–10), `GIF` (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: `XI_FORMATS_GIF_EFFORT`",
              "title": "XI_FORMATS_GIF_EFFORT",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "hdr": {
          "additionalProperties": false,
          "properties": {
            "tonemap": {
              "default": "reinhard",
              "description": "Operator mapping OpenEXR, Radiance and PFM sources to 8-bit sRGB: reinhard (extended Reinhard, white at the 99th-percentile luminance; identity for sources within [0, 1]), aces (Narkowicz's fitted ACES filmic curve) or clip. Skipped with preserve_hdr for JPEG XL (float) and AVIF (12-bit) outputs; FORMATS.md §1.4\n\nEnv: XI_FORMATS_HDR_TONEMAP",
              "enum": [
                "reinhard",
                "aces",
                "clip"
              ],
              "markdownDescription": "Operator mapping OpenEXR, Radiance and PFM sources to 8-bit sRGB: `reinhard` (extended Reinhard, white at the 99th-percentile luminance; identity for sources within [0, 1]), `aces` (Narkowicz's fitted ACES filmic curve) or `clip`. Skipped with `preserve_hdr` for JPEG XL (float) and AVIF (12-bit) outputs; FORMATS.md §1.4\n\nEnv: `XI_FORMATS_HDR_TONEMAP`",
              "title": "XI_FORMATS_HDR_TONEMAP",
              "type": "string"
            }
          },
          "type": "object"
        },
        "heic": {
          "additionalProperties": false,
          "properties": {
            "encode": {
              "default": false,
              "description": "HEIC output (HEVC patents; decode is always on)\n\nEnv: XI_FORMATS_HEIC_ENCODE",
              "markdownDescription": "HEIC output (HEVC patents; decode is always on)\n\nEnv: `XI_FORMATS_HEIC_ENCODE`",
              "title": "XI_FORMATS_HEIC_ENCODE",
              "type": "boolean"
            },
            "quality": {
              "description": "<FMT>: JPEG, WEBP, AVIF, HEIC, JXL; other formats use XI_FORMATS_QUALITY\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: XI_FORMATS_HEIC_QUALITY",
              "markdownDescription": "`<FMT>`: `JPEG`, `WEBP`, `AVIF`, `HEIC`, `JXL`; other formats use `XI_FORMATS_QUALITY`\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: `XI_FORMATS_HEIC_QUALITY`",
              "title": "XI_FORMATS_HEIC_QUALITY",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "jpeg": {
          "additionalProperties": false,
          "properties": {
            "progressive": {
              "default": true,
              "description": "Env: XI_FORMATS_JPEG_PROGRESSIVE",
              "markdownDescription": "Env: `XI_FORMATS_JPEG_PROGRESSIVE`",
              "title": "XI_FORMATS_JPEG_PROGRESSIVE",
              "type": "boolean"
            },
            "quality": {
              "description": "<FMT>: JPEG, WEBP, AVIF, HEIC, JXL; other formats use XI_FORMATS_QUALITY\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: XI_FORMATS_JPEG_QUALITY",
              "markdownDescription": "`<FMT>`: `JPEG`, `WEBP`, `AVIF`, `HEIC`, `JXL`; other formats use `XI_FORMATS_QUALITY`\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: `XI_FORMATS_JPEG_QUALITY`",
              "title": "XI_FORMATS_JPEG_QUALITY",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "jxl": {
          "additionalProperties": false,
          "properties": {
            "effort": {
              "description": "<FMT>: WEBP (0–6), AVIF (0–9), JXL (1–10), GIF (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: XI_FORMATS_JXL_EFFORT",
              "markdownDescription": "`<FMT>`: `WEBP` (0–6), `AVIF` (0–9), `JXL` (1–10), `GIF` (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: `XI_FORMATS_JXL_EFFORT`",
              "title": "XI_FORMATS_JXL_EFFORT",
              "type": "integer"
            },
            "quality": {
              "description": "<FMT>: JPEG, WEBP, AVIF, HEIC, JXL; other formats use XI_FORMATS_QUALITY\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: XI_FORMATS_JXL_QUALITY",
              "markdownDescription": "`<FMT>`: `JPEG`, `WEBP`, `AVIF`, `HEIC`, `JXL`; other formats use `XI_FORMATS_QUALITY`\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: `XI_FORMATS_JXL_QUALITY`",
              "title": "XI_FORMATS_JXL_QUALITY",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "metafile": {
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "$ref": "#/$defs/tristate",
              "default": "auto",
              "description": "**gpl image only.** Convert Windows metafiles (WMF, EMF and EMF+) to SVG with xi-metafile2svg (libwmf, LGPL-2.0-or-later; libemf2svg, GPL-2.0-only), one sandboxed subprocess per source (the Ghostscript sandbox, ADR 0011), then render the SVG like any other (FORMATS §1.8). auto converts when the converter is found and passes its startup check (--version through the sandbox), and otherwise answers 422 \"WMF and EMF input needs the gpl image (XI_FORMATS_METAFILE_ENABLED)\"; a converter that is present but fails the check logs the warning metafile conversion unavailable with the reason and never stops startup. true without a usable converter stops startup; false always answers that 422\n\nEnv: XI_FORMATS_METAFILE_ENABLED",
              "markdownDescription": "**`gpl` image only.** Convert Windows metafiles (WMF, EMF and EMF+) to SVG with `xi-metafile2svg` (libwmf, LGPL-2.0-or-later; libemf2svg, GPL-2.0-only), one sandboxed subprocess per source (the Ghostscript sandbox, ADR 0011), then render the SVG like any other (FORMATS §1.8). `auto` converts when the converter is found and passes its startup check (`--version` through the sandbox), and otherwise answers 422 \"WMF and EMF input needs the gpl image (XI_FORMATS_METAFILE_ENABLED)\"; a converter that is present but fails the check logs the warning `metafile conversion unavailable` with the reason and never stops startup. `true` without a usable converter stops startup; `false` always answers that 422\n\nEnv: `XI_FORMATS_METAFILE_ENABLED`",
              "title": "XI_FORMATS_METAFILE_ENABLED"
            },
            "max_procs": {
              "default": 2,
              "description": "Concurrent conversions (1–256), on top of the worker limit; a conversion beyond it fails fast with 429\n\nEnv: XI_FORMATS_METAFILE_MAX_PROCS",
              "markdownDescription": "Concurrent conversions (1–256), on top of the worker limit; a conversion beyond it fails fast with 429\n\nEnv: `XI_FORMATS_METAFILE_MAX_PROCS`",
              "title": "XI_FORMATS_METAFILE_MAX_PROCS",
              "type": "integer"
            },
            "path": {
              "default": "xi-metafile2svg",
              "description": "The converter: absolute path or a name looked up in PATH (the gpl image has /opt/xtandard/bin/xi-metafile2svg)\n\nEnv: XI_FORMATS_METAFILE_PATH",
              "markdownDescription": "The converter: absolute path or a name looked up in `PATH` (the gpl image has `/opt/xtandard/bin/xi-metafile2svg`)\n\nEnv: `XI_FORMATS_METAFILE_PATH`",
              "title": "XI_FORMATS_METAFILE_PATH",
              "type": "string"
            },
            "timeout": {
              "$ref": "#/$defs/duration",
              "default": "10s",
              "description": "Wall-clock limit of one conversion (100ms–10m); the process is killed with its process group and the request answers 504. Its CPU-time limit is the same, rounded up, plus one second\n\nEnv: XI_FORMATS_METAFILE_TIMEOUT",
              "markdownDescription": "Wall-clock limit of one conversion (100ms–10m); the process is killed with its process group and the request answers 504. Its CPU-time limit is the same, rounded up, plus one second\n\nEnv: `XI_FORMATS_METAFILE_TIMEOUT`",
              "title": "XI_FORMATS_METAFILE_TIMEOUT"
            }
          },
          "type": "object"
        },
        "pdf": {
          "additionalProperties": false,
          "properties": {
            "dpi": {
              "default": 72,
              "description": "Resolution of a PDF page's natural size (1–1200; 72 = one pixel per point). Resized pages render directly at the target size\n\nEnv: XI_FORMATS_PDF_DPI",
              "markdownDescription": "Resolution of a PDF page's natural size (1–1200; 72 = one pixel per point). Resized pages render directly at the target size\n\nEnv: `XI_FORMATS_PDF_DPI`",
              "title": "XI_FORMATS_PDF_DPI",
              "type": "number"
            }
          },
          "type": "object"
        },
        "png": {
          "additionalProperties": false,
          "properties": {
            "quantize": {
              "default": false,
              "description": "Env: XI_FORMATS_PNG_QUANTIZE",
              "markdownDescription": "Env: `XI_FORMATS_PNG_QUANTIZE`",
              "title": "XI_FORMATS_PNG_QUANTIZE",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "preferred": {
          "default": [
            "jpeg",
            "png",
            "gif"
          ],
          "description": "Fallback order for unencodable sources\n\nEnv: XI_FORMATS_PREFERRED",
          "items": {
            "enum": [
              "jpeg",
              "png",
              "gif",
              "webp",
              "avif",
              "heic",
              "jxl",
              "tiff",
              "bmp",
              "ico",
              "svg",
              "jpg"
            ],
            "type": "string"
          },
          "markdownDescription": "Fallback order for unencodable sources\n\nEnv: `XI_FORMATS_PREFERRED`",
          "title": "XI_FORMATS_PREFERRED",
          "type": "array"
        },
        "psd": {
          "additionalProperties": false,
          "properties": {
            "require_composite": {
              "default": false,
              "description": "Refuse (422) PSD/PSB files saved without a merged composite (\"Maximize compatibility\" off) instead of letting ImageMagick flatten the layers\n\nEnv: XI_FORMATS_PSD_REQUIRE_COMPOSITE",
              "markdownDescription": "Refuse (422) PSD/PSB files saved without a merged composite (\"Maximize compatibility\" off) instead of letting ImageMagick flatten the layers\n\nEnv: `XI_FORMATS_PSD_REQUIRE_COMPOSITE`",
              "title": "XI_FORMATS_PSD_REQUIRE_COMPOSITE",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "quality": {
          "default": 80,
          "description": "Global default\n\nEnv: XI_FORMATS_QUALITY",
          "markdownDescription": "Global default\n\nEnv: `XI_FORMATS_QUALITY`",
          "title": "XI_FORMATS_QUALITY",
          "type": "integer"
        },
        "raw": {
          "additionalProperties": false,
          "properties": {
            "preview_min_ratio": {
              "default": 1,
              "description": "Camera RAW: decode the embedded JPEG preview instead of demosaicing when it is at least this many times the size the image is scaled to, on both axes (FORMATS.md §2); 0 < ratio ≤ 100\n\nEnv: XI_FORMATS_RAW_PREVIEW_MIN_RATIO",
              "markdownDescription": "Camera RAW: decode the embedded JPEG preview instead of demosaicing when it is at least this many times the size the image is scaled to, on both axes (FORMATS.md §2); `0 < ratio ≤ 100`\n\nEnv: `XI_FORMATS_RAW_PREVIEW_MIN_RATIO`",
              "title": "XI_FORMATS_RAW_PREVIEW_MIN_RATIO",
              "type": "number"
            }
          },
          "type": "object"
        },
        "save_data_quality_delta": {
          "default": -15,
          "description": "Env: XI_FORMATS_SAVE_DATA_QUALITY_DELTA",
          "markdownDescription": "Env: `XI_FORMATS_SAVE_DATA_QUALITY_DELTA`",
          "title": "XI_FORMATS_SAVE_DATA_QUALITY_DELTA",
          "type": "integer"
        },
        "skip_processing": {
          "description": "Source formats streamed unmodified (imgproxy IMGPROXY_SKIP_PROCESSING_FORMATS)\n\nEnv: XI_FORMATS_SKIP_PROCESSING",
          "items": {
            "enum": [
              "jpeg",
              "png",
              "gif",
              "webp",
              "avif",
              "heic",
              "jxl",
              "tiff",
              "bmp",
              "ico",
              "svg",
              "jpg"
            ],
            "type": "string"
          },
          "markdownDescription": "Source formats streamed unmodified (imgproxy `IMGPROXY_SKIP_PROCESSING_FORMATS`)\n\nEnv: `XI_FORMATS_SKIP_PROCESSING`",
          "title": "XI_FORMATS_SKIP_PROCESSING",
          "type": "array"
        },
        "strict_accept": {
          "default": false,
          "description": "406 when nothing acceptable\n\nEnv: XI_FORMATS_STRICT_ACCEPT",
          "markdownDescription": "406 when nothing acceptable\n\nEnv: `XI_FORMATS_STRICT_ACCEPT`",
          "title": "XI_FORMATS_STRICT_ACCEPT",
          "type": "boolean"
        },
        "svg_passthrough": {
          "default": true,
          "description": "Env: XI_FORMATS_SVG_PASSTHROUGH",
          "markdownDescription": "Env: `XI_FORMATS_SVG_PASSTHROUGH`",
          "title": "XI_FORMATS_SVG_PASSTHROUGH",
          "type": "boolean"
        },
        "webp": {
          "additionalProperties": false,
          "properties": {
            "effort": {
              "description": "<FMT>: WEBP (0–6), AVIF (0–9), JXL (1–10), GIF (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: XI_FORMATS_WEBP_EFFORT",
              "markdownDescription": "`<FMT>`: `WEBP` (0–6), `AVIF` (0–9), `JXL` (1–10), `GIF` (1–10); see FORMATS.md\n\nDefault: webp 4, avif 4, jxl 5, gif 7\n\nEnv: `XI_FORMATS_WEBP_EFFORT`",
              "title": "XI_FORMATS_WEBP_EFFORT",
              "type": "integer"
            },
            "quality": {
              "description": "<FMT>: JPEG, WEBP, AVIF, HEIC, JXL; other formats use XI_FORMATS_QUALITY\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: XI_FORMATS_WEBP_QUALITY",
              "markdownDescription": "`<FMT>`: `JPEG`, `WEBP`, `AVIF`, `HEIC`, `JXL`; other formats use `XI_FORMATS_QUALITY`\n\nDefault: avif 60, heic 60, jxl 75, webp 80, jpeg 80\n\nEnv: `XI_FORMATS_WEBP_QUALITY`",
              "title": "XI_FORMATS_WEBP_QUALITY",
              "type": "integer"
            }
          },
          "type": "object"
        }
      },
      "type": "object"
    },
    "limits": {
      "additionalProperties": false,
      "properties": {
        "max_animation_frame_pixels": {
          "default": 16000000,
          "description": "Width × height of one frame of an animation processed as one. When set it **replaces** XI_LIMITS_MAX_SRC_PIXELS for those animations, as imgproxy's IMGPROXY_MAX_ANIMATION_FRAME_RESOLUTION does, so an 800×600 GIF of 176 frames (84.5 MP in all) is accepted; the total is then bounded by XI_LIMITS_MAX_ANIMATION_PIXELS. 0 turns it off: the animation counts all its frames against XI_LIMITS_MAX_SRC_PIXELS (imgproxy's default)\n\nEnv: XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS",
          "markdownDescription": "Width × height of one frame of an animation processed as one. When set it **replaces** `XI_LIMITS_MAX_SRC_PIXELS` for those animations, as imgproxy's `IMGPROXY_MAX_ANIMATION_FRAME_RESOLUTION` does, so an 800×600 GIF of 176 frames (84.5 MP in all) is accepted; the total is then bounded by `XI_LIMITS_MAX_ANIMATION_PIXELS`. `0` turns it off: the animation counts all its frames against `XI_LIMITS_MAX_SRC_PIXELS` (imgproxy's default)\n\nEnv: `XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS`",
          "title": "XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS",
          "type": "integer"
        },
        "max_animation_frames": {
          "default": 256,
          "description": "Frames an animation may declare (422 above it, checked on the header); 1 disables animation instead (the first frame is processed, as in imgproxy)\n\nEnv: XI_LIMITS_MAX_ANIMATION_FRAMES",
          "markdownDescription": "Frames an animation may declare (422 above it, checked on the header); `1` disables animation instead (the first frame is processed, as in imgproxy)\n\nEnv: `XI_LIMITS_MAX_ANIMATION_FRAMES`",
          "title": "XI_LIMITS_MAX_ANIMATION_FRAMES",
          "type": "integer"
        },
        "max_animation_pixels": {
          "default": 200000000,
          "description": "Width × height × frames of an animation that XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS checks instead of XI_LIMITS_MAX_SRC_PIXELS (not in imgproxy). The frame limit alone would admit 256 frames of 16 MP (4.1 gigapixels, 12–16 GB decoded); this caps one animation at about 0.6–0.8 GB of decoded frames (3–4 bytes a pixel, the planner's memory estimate). XI_PROCESSING_MEMORY_BUDGET still applies on top; no URL option changes it\n\nEnv: XI_LIMITS_MAX_ANIMATION_PIXELS",
          "markdownDescription": "Width × height × frames of an animation that `XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS` checks instead of `XI_LIMITS_MAX_SRC_PIXELS` (not in imgproxy). The frame limit alone would admit 256 frames of 16 MP (4.1 gigapixels, 12–16 GB decoded); this caps one animation at about 0.6–0.8 GB of decoded frames (3–4 bytes a pixel, the planner's memory estimate). `XI_PROCESSING_MEMORY_BUDGET` still applies on top; no URL option changes it\n\nEnv: `XI_LIMITS_MAX_ANIMATION_PIXELS`",
          "title": "XI_LIMITS_MAX_ANIMATION_PIXELS",
          "type": "integer"
        },
        "max_pdf_pages": {
          "default": 500,
          "description": "Pages a PDF may have; checked when the header is read, before any page renders (422)\n\nEnv: XI_LIMITS_MAX_PDF_PAGES",
          "markdownDescription": "Pages a PDF may have; checked when the header is read, before any page renders (422)\n\nEnv: `XI_LIMITS_MAX_PDF_PAGES`",
          "title": "XI_LIMITS_MAX_PDF_PAGES",
          "type": "integer"
        },
        "max_raw_demosaic_pixels": {
          "default": 120000000,
          "description": "Sensor width × height a camera RAW may be demosaiced at, at full or half size (102 MP medium format and 50 MP phones pass); the embedded-preview path is not affected. The decoded image also counts against XI_LIMITS_MAX_SRC_PIXELS\n\nEnv: XI_LIMITS_MAX_RAW_DEMOSAIC_PIXELS",
          "markdownDescription": "Sensor width × height a camera RAW may be demosaiced at, at full or half size (102 MP medium format and 50 MP phones pass); the embedded-preview path is not affected. The decoded image also counts against `XI_LIMITS_MAX_SRC_PIXELS`\n\nEnv: `XI_LIMITS_MAX_RAW_DEMOSAIC_PIXELS`",
          "title": "XI_LIMITS_MAX_RAW_DEMOSAIC_PIXELS",
          "type": "integer"
        },
        "max_result_dimension": {
          "default": 8192,
          "description": "Env: XI_LIMITS_MAX_RESULT_DIMENSION",
          "markdownDescription": "Env: `XI_LIMITS_MAX_RESULT_DIMENSION`",
          "title": "XI_LIMITS_MAX_RESULT_DIMENSION",
          "type": "integer"
        },
        "max_src_bytes": {
          "$ref": "#/$defs/byteSize",
          "default": "50MiB",
          "description": "Env: XI_LIMITS_MAX_SRC_BYTES",
          "markdownDescription": "Env: `XI_LIMITS_MAX_SRC_BYTES`",
          "title": "XI_LIMITS_MAX_SRC_BYTES"
        },
        "max_src_pixels": {
          "default": 50000000,
          "description": "Width × height × frames loaded, as imgproxy's IMGPROXY_MAX_SRC_RESOLUTION: a still (a multi-page still counts the pages it loads), the first frame of an animation processed as a still, and an animation processed as one only while XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS is 0. The 422 body names the limit and the numbers, e.g. source has 176 frames of 800×600 (84.5 MP); limit XI_LIMITS_MAX_SRC_PIXELS is 50 MP. A camera RAW counts what is decoded: its embedded preview, its half-size demosaic or its full demosaic, whichever the request takes (FORMATS.md §2)\n\nEnv: XI_LIMITS_MAX_SRC_PIXELS",
          "markdownDescription": "Width × height × frames loaded, as imgproxy's `IMGPROXY_MAX_SRC_RESOLUTION`: a still (a multi-page still counts the pages it loads), the first frame of an animation processed as a still, and an animation processed as one only while `XI_LIMITS_MAX_ANIMATION_FRAME_PIXELS` is `0`. The 422 body names the limit and the numbers, e.g. `source has 176 frames of 800×600 (84.5 MP); limit XI_LIMITS_MAX_SRC_PIXELS is 50 MP`. A camera RAW counts what is decoded: its embedded preview, its half-size demosaic or its full demosaic, whichever the request takes (FORMATS.md §2)\n\nEnv: `XI_LIMITS_MAX_SRC_PIXELS`",
          "title": "XI_LIMITS_MAX_SRC_PIXELS",
          "type": "integer"
        },
        "max_svg_bytes": {
          "$ref": "#/$defs/byteSize",
          "default": "5MiB",
          "description": "Env: XI_LIMITS_MAX_SVG_BYTES",
          "markdownDescription": "Env: `XI_LIMITS_MAX_SVG_BYTES`",
          "title": "XI_LIMITS_MAX_SVG_BYTES"
        },
        "max_svg_depth": {
          "default": 256,
          "description": "SVG sanitiser nesting limit\n\nEnv: XI_LIMITS_MAX_SVG_DEPTH",
          "markdownDescription": "SVG sanitiser nesting limit\n\nEnv: `XI_LIMITS_MAX_SVG_DEPTH`",
          "title": "XI_LIMITS_MAX_SVG_DEPTH",
          "type": "integer"
        },
        "max_svg_elements": {
          "default": 200000,
          "description": "SVG sanitiser element limit\n\nEnv: XI_LIMITS_MAX_SVG_ELEMENTS",
          "markdownDescription": "SVG sanitiser element limit\n\nEnv: `XI_LIMITS_MAX_SVG_ELEMENTS`",
          "title": "XI_LIMITS_MAX_SVG_ELEMENTS",
          "type": "integer"
        },
        "max_watermark_text_pixels": {
          "default": 16777216,
          "description": "Area a text watermark is rendered on (the frame, or the text width × the frame height, scaled down to this); text that does not fit is shrunk until it does\n\nEnv: XI_LIMITS_MAX_WATERMARK_TEXT_PIXELS",
          "markdownDescription": "Area a text watermark is rendered on (the frame, or the text width × the frame height, scaled down to this); text that does not fit is shrunk until it does\n\nEnv: `XI_LIMITS_MAX_WATERMARK_TEXT_PIXELS`",
          "title": "XI_LIMITS_MAX_WATERMARK_TEXT_PIXELS",
          "type": "integer"
        }
      },
      "type": "object"
    },
    "observability": {
      "additionalProperties": false,
      "properties": {
        "debug_headers": {
          "default": false,
          "description": "X-Xi-Ignored, X-Xi-Origin-Content-Length, X-Xi-Origin-Width/-Height, X-Xi-Result-Width/-Height (ARCHITECTURE §9.1)\n\nEnv: XI_OBSERVABILITY_DEBUG_HEADERS",
          "markdownDescription": "`X-Xi-Ignored`, `X-Xi-Origin-Content-Length`, `X-Xi-Origin-Width/-Height`, `X-Xi-Result-Width/-Height` (ARCHITECTURE §9.1)\n\nEnv: `XI_OBSERVABILITY_DEBUG_HEADERS`",
          "title": "XI_OBSERVABILITY_DEBUG_HEADERS",
          "type": "boolean"
        },
        "log_level": {
          "default": "info",
          "description": "Env: XI_OBSERVABILITY_LOG_LEVEL",
          "enum": [
            "debug",
            "info",
            "warn",
            "error"
          ],
          "markdownDescription": "Env: `XI_OBSERVABILITY_LOG_LEVEL`",
          "title": "XI_OBSERVABILITY_LOG_LEVEL",
          "type": "string"
        }
      },
      "type": "object"
    },
    "otel": {
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "default": false,
          "description": "Force tracing on without an endpoint variable (exporter still chosen by OTEL_TRACES_EXPORTER, e.g. console)\n\nEnv: XI_OTEL_ENABLED",
          "markdownDescription": "Force tracing on without an endpoint variable (exporter still chosen by `OTEL_TRACES_EXPORTER`, e.g. `console`)\n\nEnv: `XI_OTEL_ENABLED`",
          "title": "XI_OTEL_ENABLED",
          "type": "boolean"
        }
      },
      "type": "object"
    },
    "playground": {
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "default": false,
          "description": "Serve the playground at XI_PLAYGROUND_PATH on the admin listener (needs XI_ADMIN_ADDR unless XI_PLAYGROUND_PUBLIC); off, every playground path answers 404\n\nEnv: XI_PLAYGROUND_ENABLED",
          "markdownDescription": "Serve the playground at `XI_PLAYGROUND_PATH` on the admin listener (needs `XI_ADMIN_ADDR` unless `XI_PLAYGROUND_PUBLIC`); off, every playground path answers 404\n\nEnv: `XI_PLAYGROUND_ENABLED`",
          "title": "XI_PLAYGROUND_ENABLED",
          "type": "boolean"
        },
        "path": {
          "default": "/_playground",
          "description": "Mount path: starts with /, no trailing /, [A-Za-z0-9._~/-] only, and not an admin route\n\nEnv: XI_PLAYGROUND_PATH",
          "markdownDescription": "Mount path: starts with `/`, no trailing `/`, `[A-Za-z0-9._~/-]` only, and not an admin route\n\nEnv: `XI_PLAYGROUND_PATH`",
          "title": "XI_PLAYGROUND_PATH",
          "type": "string"
        },
        "public": {
          "default": false,
          "description": "Also serve it on the public listener (under XI_SERVER_PATH_PREFIX). Its sign endpoint then signs any URL for anyone who can reach it, which makes signing pointless: only for trusted networks and demos\n\nEnv: XI_PLAYGROUND_PUBLIC",
          "markdownDescription": "Also serve it on the public listener (under `XI_SERVER_PATH_PREFIX`). Its sign endpoint then signs any URL for anyone who can reach it, which makes signing pointless: only for trusted networks and demos\n\nEnv: `XI_PLAYGROUND_PUBLIC`",
          "title": "XI_PLAYGROUND_PUBLIC",
          "type": "boolean"
        },
        "uploads": {
          "$ref": "#/$defs/tristate",
          "default": "auto",
          "description": "POST api/upload: files dropped on the page are kept in memory (at most XI_LIMITS_MAX_SRC_BYTES each, 64 files or 256 MiB, 10 minutes) as playground://uploads/<id> sources that only api/render can read. false disables uploads; true needs XI_PLAYGROUND_ENABLED\n\nEnv: XI_PLAYGROUND_UPLOADS",
          "markdownDescription": "`POST api/upload`: files dropped on the page are kept in memory (at most `XI_LIMITS_MAX_SRC_BYTES` each, 64 files or 256 MiB, 10 minutes) as `playground://uploads/<id>` sources that only `api/render` can read. `false` disables uploads; `true` needs `XI_PLAYGROUND_ENABLED`\n\nEnv: `XI_PLAYGROUND_UPLOADS`",
          "title": "XI_PLAYGROUND_UPLOADS"
        }
      },
      "type": "object"
    },
    "presets": {
      "additionalProperties": false,
      "description": "name=opts,name2=opts (imgproxy syntax); YAML presets: {name: opts}\n\nEnv: XI_PRESETS",
      "markdownDescription": "`name=opts,name2=opts` (imgproxy syntax); YAML `presets: {name: opts}`\n\nEnv: `XI_PRESETS`",
      "patternProperties": {
        "^[^:/]+$": {
          "minLength": 1,
          "type": "string"
        }
      },
      "title": "XI_PRESETS",
      "type": "object"
    },
    "presets_file": {
      "default": "",
      "description": "One name=options per line; blank lines and # comments skipped; a name also in XI_PRESETS is an error\n\nEnv: XI_PRESETS_FILE",
      "markdownDescription": "One `name=options` per line; blank lines and `#` comments skipped; a name also in `XI_PRESETS` is an error\n\nEnv: `XI_PRESETS_FILE`",
      "title": "XI_PRESETS_FILE",
      "type": "string"
    },
    "presets_only": {
      "default": false,
      "description": "Only preset URLs allowed\n\nEnv: XI_PRESETS_ONLY",
      "markdownDescription": "Only preset URLs allowed\n\nEnv: `XI_PRESETS_ONLY`",
      "title": "XI_PRESETS_ONLY",
      "type": "boolean"
    },
    "processing": {
      "additionalProperties": false,
      "properties": {
        "auto_rotate": {
          "default": true,
          "description": "Env: XI_PROCESSING_AUTO_ROTATE",
          "markdownDescription": "Env: `XI_PROCESSING_AUTO_ROTATE`",
          "title": "XI_PROCESSING_AUTO_ROTATE",
          "type": "boolean"
        },
        "color_profiles_dir": {
          "default": "",
          "description": "Custom ICC files\n\nEnv: XI_PROCESSING_COLOR_PROFILES_DIR",
          "markdownDescription": "Custom ICC files\n\nEnv: `XI_PROCESSING_COLOR_PROFILES_DIR`",
          "title": "XI_PROCESSING_COLOR_PROFILES_DIR",
          "type": "string"
        },
        "go_memory_fraction": {
          "default": 0.3,
          "description": "Sets the Go memory limit (GOMEMLIMIT) at startup to this fraction of the cgroup memory limit (v2 memory.max, v1 memory.limit_in_bytes; the smallest from the process's cgroup up). A GOMEMLIMIT you set wins; without a limit nothing changes. Logged once (go memory limit set); [OPERATIONS.md §1.3](OPERATIONS.md#13-memory)\n\nEnv: XI_PROCESSING_GO_MEMORY_FRACTION",
          "markdownDescription": "Sets the Go memory limit (`GOMEMLIMIT`) at startup to this fraction of the cgroup memory limit (v2 `memory.max`, v1 `memory.limit_in_bytes`; the smallest from the process's cgroup up). A `GOMEMLIMIT` you set wins; without a limit nothing changes. Logged once (`go memory limit set`); [OPERATIONS.md §1.3](OPERATIONS.md#13-memory)\n\nEnv: `XI_PROCESSING_GO_MEMORY_FRACTION`",
          "title": "XI_PROCESSING_GO_MEMORY_FRACTION",
          "type": "number"
        },
        "keep_copyright": {
          "default": true,
          "description": "Env: XI_PROCESSING_KEEP_COPYRIGHT",
          "markdownDescription": "Env: `XI_PROCESSING_KEEP_COPYRIGHT`",
          "title": "XI_PROCESSING_KEEP_COPYRIGHT",
          "type": "boolean"
        },
        "linear_resize": {
          "default": false,
          "description": "Resize in linear light. **Not implemented yet**: validated and accepted, but resizing is always in the image's own colour space (ROADMAP backlog)\n\nEnv: XI_PROCESSING_LINEAR_RESIZE",
          "markdownDescription": "Resize in linear light. **Not implemented yet**: validated and accepted, but resizing is always in the image's own colour space (ROADMAP backlog)\n\nEnv: `XI_PROCESSING_LINEAR_RESIZE`",
          "title": "XI_PROCESSING_LINEAR_RESIZE",
          "type": "boolean"
        },
        "memory_budget": {
          "$ref": "#/$defs/byteSize",
          "default": "0",
          "description": "Optional weighted admission budget in bytes\n\nEnv: XI_PROCESSING_MEMORY_BUDGET",
          "markdownDescription": "Optional weighted admission budget in bytes\n\nEnv: `XI_PROCESSING_MEMORY_BUDGET`",
          "title": "XI_PROCESSING_MEMORY_BUDGET"
        },
        "preserve_hdr": {
          "default": false,
          "description": "Env: XI_PROCESSING_PRESERVE_HDR",
          "markdownDescription": "Env: `XI_PROCESSING_PRESERVE_HDR`",
          "title": "XI_PROCESSING_PRESERVE_HDR",
          "type": "boolean"
        },
        "queue_size": {
          "default": -1,
          "description": "Waiting jobs; beyond it requests fail fast with 429 (-1 = workers×16, 0 = no queue)\n\nEnv: XI_PROCESSING_QUEUE_SIZE",
          "markdownDescription": "Waiting jobs; beyond it requests fail fast with 429 (`-1` = workers×16, `0` = no queue)\n\nEnv: `XI_PROCESSING_QUEUE_SIZE`",
          "title": "XI_PROCESSING_QUEUE_SIZE",
          "type": "integer"
        },
        "queue_timeout": {
          "$ref": "#/$defs/duration",
          "default": "5s",
          "description": "Max wait for a worker, then 503\n\nEnv: XI_PROCESSING_QUEUE_TIMEOUT",
          "markdownDescription": "Max wait for a worker, then 503\n\nEnv: `XI_PROCESSING_QUEUE_TIMEOUT`",
          "title": "XI_PROCESSING_QUEUE_TIMEOUT"
        },
        "strip_color_profile": {
          "default": true,
          "description": "Env: XI_PROCESSING_STRIP_COLOR_PROFILE",
          "markdownDescription": "Env: `XI_PROCESSING_STRIP_COLOR_PROFILE`",
          "title": "XI_PROCESSING_STRIP_COLOR_PROFILE",
          "type": "boolean"
        },
        "strip_metadata": {
          "default": true,
          "description": "Env: XI_PROCESSING_STRIP_METADATA",
          "markdownDescription": "Env: `XI_PROCESSING_STRIP_METADATA`",
          "title": "XI_PROCESSING_STRIP_METADATA",
          "type": "boolean"
        },
        "watermark_font": {
          "default": "sans 16",
          "description": "Pango font description of text watermarks (imgproxy's default); a request's font takes the family or size it lacks from it. The image ships DejaVu Sans and DejaVu Sans Mono (sans, monospace) through fontconfig\n\nEnv: XI_PROCESSING_WATERMARK_FONT",
          "markdownDescription": "Pango font description of text watermarks (imgproxy's default); a request's font takes the family or size it lacks from it. The image ships DejaVu Sans and DejaVu Sans Mono (`sans`, `monospace`) through fontconfig\n\nEnv: `XI_PROCESSING_WATERMARK_FONT`",
          "title": "XI_PROCESSING_WATERMARK_FONT",
          "type": "string"
        },
        "watermark_font_file": {
          "default": "",
          "description": "Absolute path of a font file (TTF/OTF) registered with fontconfig for text watermarks, in addition to the fonts under /opt/xtandard/share/fonts; requests select it by family name\n\nEnv: XI_PROCESSING_WATERMARK_FONT_FILE",
          "markdownDescription": "Absolute path of a font file (TTF/OTF) registered with fontconfig for text watermarks, in addition to the fonts under `/opt/xtandard/share/fonts`; requests select it by family name\n\nEnv: `XI_PROCESSING_WATERMARK_FONT_FILE`",
          "title": "XI_PROCESSING_WATERMARK_FONT_FILE",
          "type": "string"
        },
        "watermark_opacity": {
          "default": 1,
          "description": "Multiplies every request's watermark opacity (configured image, per-request image and text)\n\nEnv: XI_PROCESSING_WATERMARK_OPACITY",
          "markdownDescription": "Multiplies every request's watermark opacity (configured image, per-request image and text)\n\nEnv: `XI_PROCESSING_WATERMARK_OPACITY`",
          "title": "XI_PROCESSING_WATERMARK_OPACITY",
          "type": "number"
        },
        "watermark_source": {
          "default": "",
          "description": "Default watermark image (source string, resolved against the sources). Fetched and decoded once at startup, never refreshed; a watermark that cannot be loaded stops startup. The playground's api/meta reports only whether it is set (watermark), for the Examples that need it\n\nEnv: XI_PROCESSING_WATERMARK_SOURCE",
          "markdownDescription": "Default watermark image (source string, resolved against the sources). Fetched and decoded once at startup, never refreshed; a watermark that cannot be loaded stops startup. The playground's `api/meta` reports only whether it is set (`watermark`), for the Examples that need it\n\nEnv: `XI_PROCESSING_WATERMARK_SOURCE`",
          "title": "XI_PROCESSING_WATERMARK_SOURCE",
          "type": "string"
        },
        "workers": {
          "default": 0,
          "description": "Concurrent processing jobs (0 = GOMAXPROCS)\n\nEnv: XI_PROCESSING_WORKERS",
          "markdownDescription": "Concurrent processing jobs (`0` = GOMAXPROCS)\n\nEnv: `XI_PROCESSING_WORKERS`",
          "title": "XI_PROCESSING_WORKERS",
          "type": "integer"
        }
      },
      "type": "object"
    },
    "role": {
      "default": "all",
      "description": "What this instance serves (ADR 0012): all; image (video transformations answer 404, so a load balancer routes them elsewhere); video (only video transformations and the parts of their HLS and DASH streams under /_stream/, plus /healthz, /readyz and the admin listener; every other request answers 404; needs XI_VIDEO_TRANSFORM_ENABLED). One binary and image serve every role\n\nEnv: XI_ROLE",
      "enum": [
        "all",
        "image",
        "video"
      ],
      "markdownDescription": "What this instance serves (ADR 0012): `all`; `image` (video transformations answer 404, so a load balancer routes them elsewhere); `video` (only video transformations and the parts of their HLS and DASH streams under `/_stream/`, plus `/healthz`, `/readyz` and the admin listener; every other request answers 404; needs `XI_VIDEO_TRANSFORM_ENABLED`). One binary and image serve every role\n\nEnv: `XI_ROLE`",
      "title": "XI_ROLE",
      "type": "string"
    },
    "sandbox": {
      "additionalProperties": false,
      "properties": {
        "detect_emulation": {
          "default": true,
          "description": "Drop XI_SANDBOX_MAX_AS when the server runs under CPU emulation (Rosetta for Linux, e.g. an amd64 image on Apple silicon under OrbStack or Docker Desktop; QEMU user mode): the translator maps its own memory inside the subprocess and fails under the limit (rosetta error: mmap_anonymous_rw mmap failed). Startup logs emulation and a warning sandbox without an address-space limit. false keeps the limit everywhere. The trade-off: under emulation a hostile document can make one subprocess allocate up to what the container allows (bounded by its CPU time and the timeout); set a container memory limit\n\nEnv: XI_SANDBOX_DETECT_EMULATION",
          "markdownDescription": "Drop `XI_SANDBOX_MAX_AS` when the server runs under CPU emulation (Rosetta for Linux, e.g. an amd64 image on Apple silicon under OrbStack or Docker Desktop; QEMU user mode): the translator maps its own memory inside the subprocess and fails under the limit (`rosetta error: mmap_anonymous_rw mmap failed`). Startup logs `emulation` and a warning `sandbox without an address-space limit`. `false` keeps the limit everywhere. The trade-off: under emulation a hostile document can make one subprocess allocate up to what the container allows (bounded by its CPU time and the timeout); set a container memory limit\n\nEnv: `XI_SANDBOX_DETECT_EMULATION`",
          "title": "XI_SANDBOX_DETECT_EMULATION",
          "type": "boolean"
        },
        "max_as": {
          "$ref": "#/$defs/byteSize",
          "default": "1GiB",
          "description": "Address-space limit (RLIMIT_AS) of each sandboxed subprocess (Ghostscript, the metafile converter; ADR 0011), plus twice the output the render may produce (capped at 16 GiB). 0 turns it off; the CPU-time, process, file-size and open-file limits, the timeout and no-new-privileges stay. Otherwise 64MiB–1TiB. Startup logs the value as rlimit_as (and rlimit_as_off with the reason when there is none)\n\nEnv: XI_SANDBOX_MAX_AS",
          "markdownDescription": "Address-space limit (`RLIMIT_AS`) of each sandboxed subprocess (Ghostscript, the metafile converter; ADR 0011), plus twice the output the render may produce (capped at 16 GiB). `0` turns it off; the CPU-time, process, file-size and open-file limits, the timeout and no-new-privileges stay. Otherwise 64MiB–1TiB. Startup logs the value as `rlimit_as` (and `rlimit_as_off` with the reason when there is none)\n\nEnv: `XI_SANDBOX_MAX_AS`",
          "title": "XI_SANDBOX_MAX_AS"
        }
      },
      "type": "object"
    },
    "security": {
      "additionalProperties": false,
      "properties": {
        "allow_raising_limits": {
          "default": false,
          "description": "Let them exceed config\n\nEnv: XI_SECURITY_ALLOW_RAISING_LIMITS",
          "markdownDescription": "Let them exceed config\n\nEnv: `XI_SECURITY_ALLOW_RAISING_LIMITS`",
          "title": "XI_SECURITY_ALLOW_RAISING_LIMITS",
          "type": "boolean"
        },
        "allow_security_options": {
          "default": false,
          "description": "Honour per-request limit options\n\nEnv: XI_SECURITY_ALLOW_SECURITY_OPTIONS",
          "markdownDescription": "Honour per-request limit options\n\nEnv: `XI_SECURITY_ALLOW_SECURITY_OPTIONS`",
          "title": "XI_SECURITY_ALLOW_SECURITY_OPTIONS",
          "type": "boolean"
        },
        "allowed_dprs": {
          "default": [
            1
          ],
          "description": "Restricted-policy device pixel ratios (0 < dpr ≤ 8); unset and 1 are always allowed\n\nEnv: XI_SECURITY_ALLOWED_DPRS",
          "items": {
            "type": "number"
          },
          "markdownDescription": "Restricted-policy device pixel ratios (0 < dpr ≤ 8); unset and `1` are always allowed\n\nEnv: `XI_SECURITY_ALLOWED_DPRS`",
          "title": "XI_SECURITY_ALLOWED_DPRS",
          "type": "array"
        },
        "allowed_heights": {
          "$ref": "#/$defs/intList",
          "description": "Restricted-policy heights\n\nDefault: same as XI_SECURITY_ALLOWED_WIDTHS\n\nEnv: XI_SECURITY_ALLOWED_HEIGHTS",
          "markdownDescription": "Restricted-policy heights\n\nDefault: same as `XI_SECURITY_ALLOWED_WIDTHS`\n\nEnv: `XI_SECURITY_ALLOWED_HEIGHTS`",
          "title": "XI_SECURITY_ALLOWED_HEIGHTS"
        },
        "allowed_processing_options": {
          "description": "imgproxy dialect: the only option names a URL may use, as written (w does not allow width); others are 403. The options a preset expands to are not checked, preset/pr itself is\n\nEnv: XI_SECURITY_ALLOWED_PROCESSING_OPTIONS",
          "items": {
            "type": "string"
          },
          "markdownDescription": "imgproxy dialect: the only option names a URL may use, as written (`w` does not allow `width`); others are 403. The options a preset expands to are not checked, `preset`/`pr` itself is\n\nEnv: `XI_SECURITY_ALLOWED_PROCESSING_OPTIONS`",
          "title": "XI_SECURITY_ALLOWED_PROCESSING_OPTIONS",
          "type": "array"
        },
        "allowed_qualities": {
          "$ref": "#/$defs/intList",
          "default": [
            5,
            10,
            15,
            20,
            25,
            30,
            35,
            40,
            45,
            50,
            55,
            60,
            65,
            70,
            75,
            80,
            85,
            90,
            95,
            100
          ],
          "description": "Restricted-policy qualities (5, 10, …, 100)\n\nEnv: XI_SECURITY_ALLOWED_QUALITIES",
          "markdownDescription": "Restricted-policy qualities (5, 10, …, 100)\n\nEnv: `XI_SECURITY_ALLOWED_QUALITIES`",
          "title": "XI_SECURITY_ALLOWED_QUALITIES"
        },
        "allowed_widths": {
          "$ref": "#/$defs/intList",
          "default": [
            16,
            32,
            48,
            64,
            96,
            128,
            256,
            384,
            640,
            750,
            828,
            1080,
            1200,
            1920,
            2048,
            3840
          ],
          "description": "Restricted-policy widths (Next.js device + image sizes); logged at startup\n\nEnv: XI_SECURITY_ALLOWED_WIDTHS",
          "markdownDescription": "Restricted-policy widths (Next.js device + image sizes); logged at startup\n\nEnv: `XI_SECURITY_ALLOWED_WIDTHS`",
          "title": "XI_SECURITY_ALLOWED_WIDTHS"
        },
        "secret": {
          "$ref": "#/$defs/secret",
          "description": "Require Authorization: Bearer <secret> on every public-listener request except /healthz and /readyz (CORS preflights are answered first); anything else is 403. For a CDN or proxy in front that adds the header. The playground's own renders are exempt\n\nEnv: XI_SECURITY_SECRET (or XI_SECURITY_SECRET_FILE)",
          "markdownDescription": "Require `Authorization: Bearer <secret>` on every public-listener request except `/healthz` and `/readyz` (CORS preflights are answered first); anything else is 403. For a CDN or proxy in front that adds the header. The playground's own renders are exempt\n\nEnv: `XI_SECURITY_SECRET` (or `XI_SECURITY_SECRET_FILE`)",
          "title": "XI_SECURITY_SECRET"
        },
        "unsigned_policy": {
          "default": "restricted",
          "description": "allow | restricted | deny for every request that carries no verified signature, in any dialect (including imgproxy URLs when XI_SIGNING_REQUIRED is false)\n\nEnv: XI_SECURITY_UNSIGNED_POLICY",
          "enum": [
            "allow",
            "restricted",
            "deny"
          ],
          "markdownDescription": "`allow` | `restricted` | `deny` for every request that carries no verified signature, in any dialect (including imgproxy URLs when `XI_SIGNING_REQUIRED` is false)\n\nEnv: `XI_SECURITY_UNSIGNED_POLICY`",
          "title": "XI_SECURITY_UNSIGNED_POLICY",
          "type": "string"
        }
      },
      "type": "object"
    },
    "server": {
      "additionalProperties": false,
      "properties": {
        "addr": {
          "default": ":8080",
          "description": "Listen address\n\nEnv: XI_SERVER_ADDR",
          "markdownDescription": "Listen address\n\nEnv: `XI_SERVER_ADDR`",
          "title": "XI_SERVER_ADDR",
          "type": "string"
        },
        "cors_origins": {
          "description": "Origins browsers may read responses from (* for any): a request whose Origin is listed gets Access-Control-Allow-Origin (*, or the origin with Vary: Origin) and the range and validator headers exposed, so HLS and DASH players in another origin's page can load playlists and segments; a preflight (OPTIONS for GET/HEAD) is answered 204. Empty: no CORS headers\n\nEnv: XI_SERVER_CORS_ORIGINS",
          "items": {
            "type": "string"
          },
          "markdownDescription": "Origins browsers may read responses from (`*` for any): a request whose `Origin` is listed gets `Access-Control-Allow-Origin` (`*`, or the origin with `Vary: Origin`) and the range and validator headers exposed, so HLS and DASH players in another origin's page can load playlists and segments; a preflight (`OPTIONS` for `GET`/`HEAD`) is answered 204. Empty: no CORS headers\n\nEnv: `XI_SERVER_CORS_ORIGINS`",
          "title": "XI_SERVER_CORS_ORIGINS",
          "type": "array"
        },
        "idle_timeout": {
          "$ref": "#/$defs/duration",
          "default": "1m30s",
          "description": "Keep-alive idle\n\nEnv: XI_SERVER_IDLE_TIMEOUT",
          "markdownDescription": "Keep-alive idle\n\nEnv: `XI_SERVER_IDLE_TIMEOUT`",
          "title": "XI_SERVER_IDLE_TIMEOUT"
        },
        "max_connections": {
          "default": 4096,
          "description": "Open connections on the public listener (the admin listener is not counted). At the cap the server stops accepting: new clients wait in the kernel's accept backlog until a connection closes, they are not refused. Idle keep-alive and HTTP/2 connections hold their slot until XI_SERVER_IDLE_TIMEOUT; one HTTP/2 connection carries many requests\n\nEnv: XI_SERVER_MAX_CONNECTIONS",
          "markdownDescription": "Open connections on the public listener (the admin listener is not counted). At the cap the server stops accepting: new clients wait in the kernel's accept backlog until a connection closes, they are not refused. Idle keep-alive and HTTP/2 connections hold their slot until `XI_SERVER_IDLE_TIMEOUT`; one HTTP/2 connection carries many requests\n\nEnv: `XI_SERVER_MAX_CONNECTIONS`",
          "title": "XI_SERVER_MAX_CONNECTIONS",
          "type": "integer"
        },
        "max_header_bytes": {
          "$ref": "#/$defs/byteSize",
          "default": "32KiB",
          "description": "Env: XI_SERVER_MAX_HEADER_BYTES",
          "markdownDescription": "Env: `XI_SERVER_MAX_HEADER_BYTES`",
          "title": "XI_SERVER_MAX_HEADER_BYTES"
        },
        "path_prefix": {
          "default": "",
          "description": "Global prefix stripped before routing\n\nEnv: XI_SERVER_PATH_PREFIX",
          "markdownDescription": "Global prefix stripped before routing\n\nEnv: `XI_SERVER_PATH_PREFIX`",
          "title": "XI_SERVER_PATH_PREFIX",
          "type": "string"
        },
        "query_location_max_length": {
          "default": 2048,
          "description": "Longest equivalent GET URL (path and query) a QUERY response carries as Location; longer, or a query with pdf_password, sends none. 0: never. At most 8192 (the URL limit)\n\nEnv: XI_SERVER_QUERY_LOCATION_MAX_LENGTH",
          "markdownDescription": "Longest equivalent `GET` URL (path and query) a `QUERY` response carries as `Location`; longer, or a query with `pdf_password`, sends none. `0`: never. At most `8192` (the URL limit)\n\nEnv: `XI_SERVER_QUERY_LOCATION_MAX_LENGTH`",
          "title": "XI_SERVER_QUERY_LOCATION_MAX_LENGTH",
          "type": "integer"
        },
        "query_max_body_bytes": {
          "$ref": "#/$defs/byteSize",
          "default": "64KiB",
          "description": "Largest QUERY content; more is 413\n\nEnv: XI_SERVER_QUERY_MAX_BODY_BYTES",
          "markdownDescription": "Largest `QUERY` content; more is 413\n\nEnv: `XI_SERVER_QUERY_MAX_BODY_BYTES`",
          "title": "XI_SERVER_QUERY_MAX_BODY_BYTES"
        },
        "query_method": {
          "default": true,
          "description": "Accept the HTTP QUERY method (RFC 10008) on the query dialect's endpoints (/q/, /q/video, /q/<imaginary endpoint>): the parameters travel in the request content (application/x-www-form-urlencoded or application/json) instead of the URL, with the same signing, unsigned policy, limits and result cache entry as the equivalent GET ([URL-DIALECTS §4.4](URL-DIALECTS.md#44-http-query)). false: QUERY is 405 everywhere, as before\n\nEnv: XI_SERVER_QUERY_METHOD",
          "markdownDescription": "Accept the HTTP `QUERY` method (RFC 10008) on the query dialect's endpoints (`/q/`, `/q/video`, `/q/<imaginary endpoint>`): the parameters travel in the request content (`application/x-www-form-urlencoded` or `application/json`) instead of the URL, with the same signing, unsigned policy, limits and result cache entry as the equivalent `GET` ([URL-DIALECTS §4.4](URL-DIALECTS.md#44-http-query)). `false`: `QUERY` is 405 everywhere, as before\n\nEnv: `XI_SERVER_QUERY_METHOD`",
          "title": "XI_SERVER_QUERY_METHOD",
          "type": "boolean"
        },
        "read_header_timeout": {
          "$ref": "#/$defs/duration",
          "default": "5s",
          "description": "Slowloris guard\n\nEnv: XI_SERVER_READ_HEADER_TIMEOUT",
          "markdownDescription": "Slowloris guard\n\nEnv: `XI_SERVER_READ_HEADER_TIMEOUT`",
          "title": "XI_SERVER_READ_HEADER_TIMEOUT"
        },
        "read_timeout": {
          "$ref": "#/$defs/duration",
          "default": "15s",
          "description": "Env: XI_SERVER_READ_TIMEOUT",
          "markdownDescription": "Env: `XI_SERVER_READ_TIMEOUT`",
          "title": "XI_SERVER_READ_TIMEOUT"
        },
        "ready_queue_threshold": {
          "default": 0,
          "description": "/readyz answers 503 busy while the image processing queue is at least this % full: running plus waiting jobs over XI_PROCESSING_WORKERS + XI_PROCESSING_QUEUE_SIZE places (1–100). Checked on every probe with no hysteresis, so the probe's own thresholds (failureThreshold, successThreshold) are the only damping. Video transformations have their own pool and do not count. [OPERATIONS.md §3.5](OPERATIONS.md#35-readiness-and-drain)\n\nEnv: XI_SERVER_READY_QUEUE_THRESHOLD",
          "markdownDescription": "`/readyz` answers 503 `busy` while the image processing queue is at least this % full: running plus waiting jobs over `XI_PROCESSING_WORKERS` + `XI_PROCESSING_QUEUE_SIZE` places (1–100). Checked on every probe with no hysteresis, so the probe's own thresholds (`failureThreshold`, `successThreshold`) are the only damping. Video transformations have their own pool and do not count. [OPERATIONS.md §3.5](OPERATIONS.md#35-readiness-and-drain)\n\nEnv: `XI_SERVER_READY_QUEUE_THRESHOLD`",
          "title": "XI_SERVER_READY_QUEUE_THRESHOLD",
          "type": "integer"
        },
        "request_timeout": {
          "$ref": "#/$defs/duration",
          "default": "20s",
          "description": "Whole-request deadline\n\nEnv: XI_SERVER_REQUEST_TIMEOUT",
          "markdownDescription": "Whole-request deadline\n\nEnv: `XI_SERVER_REQUEST_TIMEOUT`",
          "title": "XI_SERVER_REQUEST_TIMEOUT"
        },
        "shutdown_delay": {
          "$ref": "#/$defs/duration",
          "default": "5s",
          "description": "Readiness goes 503 this long before listener closes\n\nEnv: XI_SERVER_SHUTDOWN_DELAY",
          "markdownDescription": "Readiness goes 503 this long before listener closes\n\nEnv: `XI_SERVER_SHUTDOWN_DELAY`",
          "title": "XI_SERVER_SHUTDOWN_DELAY"
        },
        "shutdown_timeout": {
          "$ref": "#/$defs/duration",
          "default": "30s",
          "description": "Drain limit\n\nEnv: XI_SERVER_SHUTDOWN_TIMEOUT",
          "markdownDescription": "Drain limit\n\nEnv: `XI_SERVER_SHUTDOWN_TIMEOUT`",
          "title": "XI_SERVER_SHUTDOWN_TIMEOUT"
        },
        "write_timeout": {
          "$ref": "#/$defs/duration",
          "description": "Default: request timeout + 5s\n\nEnv: XI_SERVER_WRITE_TIMEOUT",
          "markdownDescription": "Default: request timeout + `5s`\n\nEnv: `XI_SERVER_WRITE_TIMEOUT`",
          "title": "XI_SERVER_WRITE_TIMEOUT"
        }
      },
      "type": "object"
    },
    "signing": {
      "additionalProperties": false,
      "properties": {
        "keys": {
          "description": "Hex key/salt pairs (same count); enables imgproxy-scheme and xi_sig verification\n\nEnv: XI_SIGNING_KEYS (or XI_SIGNING_KEYS_FILE)",
          "items": {
            "$ref": "#/$defs/hexKey"
          },
          "markdownDescription": "Hex key/salt pairs (same count); enables imgproxy-scheme and `xi_sig` verification\n\nEnv: `XI_SIGNING_KEYS` (or `XI_SIGNING_KEYS_FILE`)",
          "title": "XI_SIGNING_KEYS",
          "type": "array"
        },
        "required": {
          "$ref": "#/$defs/tristate",
          "default": "auto",
          "description": "Reject unsigned imgproxy URLs\n\nEnv: XI_SIGNING_REQUIRED",
          "markdownDescription": "Reject unsigned imgproxy URLs\n\nEnv: `XI_SIGNING_REQUIRED`",
          "title": "XI_SIGNING_REQUIRED"
        },
        "salts": {
          "description": "Hex key/salt pairs (same count); enables imgproxy-scheme and xi_sig verification\n\nEnv: XI_SIGNING_SALTS (or XI_SIGNING_SALTS_FILE)",
          "items": {
            "$ref": "#/$defs/hexKey"
          },
          "markdownDescription": "Hex key/salt pairs (same count); enables imgproxy-scheme and `xi_sig` verification\n\nEnv: `XI_SIGNING_SALTS` (or `XI_SIGNING_SALTS_FILE`)",
          "title": "XI_SIGNING_SALTS",
          "type": "array"
        },
        "size": {
          "default": 32,
          "description": "Signature bytes before base64 (imgproxy SIGNATURE_SIZE)\n\nEnv: XI_SIGNING_SIZE",
          "markdownDescription": "Signature bytes before base64 (imgproxy `SIGNATURE_SIZE`)\n\nEnv: `XI_SIGNING_SIZE`",
          "title": "XI_SIGNING_SIZE",
          "type": "integer"
        },
        "trusted": {
          "description": "Signatures always accepted\n\nEnv: XI_SIGNING_TRUSTED (or XI_SIGNING_TRUSTED_FILE)",
          "items": {
            "$ref": "#/$defs/secret"
          },
          "markdownDescription": "Signatures always accepted\n\nEnv: `XI_SIGNING_TRUSTED` (or `XI_SIGNING_TRUSTED_FILE`)",
          "title": "XI_SIGNING_TRUSTED",
          "type": "array"
        }
      },
      "type": "object"
    },
    "sources": {
      "additionalProperties": false,
      "patternProperties": {
        "^[a-z0-9][a-z0-9-]*$": {
          "$ref": "#/$defs/source"
        }
      },
      "type": "object"
    },
    "video": {
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "$ref": "#/$defs/tristate",
          "default": "auto",
          "description": "Video sources (MP4, MOV, WebM, MKV, AVI; FORMATS.md §1.5): auto decodes them when the image has FFmpeg (the full flavor) and answers 422 otherwise; true fails startup without FFmpeg; false answers 422 in every image\n\nEnv: XI_VIDEO_ENABLED",
          "markdownDescription": "Video sources (MP4, MOV, WebM, MKV, AVI; FORMATS.md §1.5): `auto` decodes them when the image has FFmpeg (the `full` flavor) and answers 422 otherwise; `true` fails startup without FFmpeg; `false` answers 422 in every image\n\nEnv: `XI_VIDEO_ENABLED`",
          "title": "XI_VIDEO_ENABLED"
        },
        "encode": {
          "additionalProperties": false,
          "properties": {
            "av1_crf": {
              "default": 36,
              "description": "CRF of AV1-in-MP4 output (libaom, the full image) (0–63)\n\nEnv: XI_VIDEO_ENCODE_AV1_CRF",
              "markdownDescription": "CRF of AV1-in-MP4 output (libaom, the `full` image) (0–63)\n\nEnv: `XI_VIDEO_ENCODE_AV1_CRF`",
              "title": "XI_VIDEO_ENCODE_AV1_CRF",
              "type": "integer"
            },
            "av1_speed": {
              "default": 8,
              "description": "libaom cpu-used in its realtime mode (0–10; higher is faster and larger)\n\nEnv: XI_VIDEO_ENCODE_AV1_SPEED",
              "markdownDescription": "libaom `cpu-used` in its realtime mode (0–10; higher is faster and larger)\n\nEnv: `XI_VIDEO_ENCODE_AV1_SPEED`",
              "title": "XI_VIDEO_ENCODE_AV1_SPEED",
              "type": "integer"
            },
            "h264_crf": {
              "default": 25,
              "description": "CRF of H.264 MP4 output (libx264, the gpl image) (0–51)\n\nEnv: XI_VIDEO_ENCODE_H264_CRF",
              "markdownDescription": "CRF of H.264 MP4 output (libx264, the `gpl` image) (0–51)\n\nEnv: `XI_VIDEO_ENCODE_H264_CRF`",
              "title": "XI_VIDEO_ENCODE_H264_CRF",
              "type": "integer"
            },
            "h264_preset": {
              "default": "veryfast",
              "description": "x264 preset (ultrafast … placebo)\n\nEnv: XI_VIDEO_ENCODE_H264_PRESET",
              "markdownDescription": "x264 preset (`ultrafast` … `placebo`)\n\nEnv: `XI_VIDEO_ENCODE_H264_PRESET`",
              "title": "XI_VIDEO_ENCODE_H264_PRESET",
              "type": "string"
            },
            "hevc_crf": {
              "default": 28,
              "description": "CRF of HEVC output (libx265, the gpl image) (0–51)\n\nEnv: XI_VIDEO_ENCODE_HEVC_CRF",
              "markdownDescription": "CRF of HEVC output (libx265, the `gpl` image) (0–51)\n\nEnv: `XI_VIDEO_ENCODE_HEVC_CRF`",
              "title": "XI_VIDEO_ENCODE_HEVC_CRF",
              "type": "integer"
            },
            "hevc_preset": {
              "default": "veryfast",
              "description": "x265 preset (ultrafast … placebo)\n\nEnv: XI_VIDEO_ENCODE_HEVC_PRESET",
              "markdownDescription": "x265 preset (`ultrafast` … `placebo`)\n\nEnv: `XI_VIDEO_ENCODE_HEVC_PRESET`",
              "title": "XI_VIDEO_ENCODE_HEVC_PRESET",
              "type": "string"
            },
            "max_duration": {
              "$ref": "#/$defs/duration",
              "default": "1m0s",
              "description": "Longest video output, the sum of the frame delays (422)\n\nEnv: XI_VIDEO_ENCODE_MAX_DURATION",
              "markdownDescription": "Longest video output, the sum of the frame delays (422)\n\nEnv: `XI_VIDEO_ENCODE_MAX_DURATION`",
              "title": "XI_VIDEO_ENCODE_MAX_DURATION"
            },
            "max_frames": {
              "default": 1000,
              "description": "Most frames a video output may have (422)\n\nEnv: XI_VIDEO_ENCODE_MAX_FRAMES",
              "markdownDescription": "Most frames a video output may have (422)\n\nEnv: `XI_VIDEO_ENCODE_MAX_FRAMES`",
              "title": "XI_VIDEO_ENCODE_MAX_FRAMES",
              "type": "integer"
            },
            "max_pixels": {
              "default": 8294400,
              "description": "Largest frame of a video output, width × height (4K UHD) (422)\n\nEnv: XI_VIDEO_ENCODE_MAX_PIXELS",
              "markdownDescription": "Largest frame of a video output, width × height (4K UHD) (422)\n\nEnv: `XI_VIDEO_ENCODE_MAX_PIXELS`",
              "title": "XI_VIDEO_ENCODE_MAX_PIXELS",
              "type": "integer"
            },
            "vp9_crf": {
              "default": 36,
              "description": "CRF of WebM (VP9, libvpx) output when the request sets no quality (0–63). A request quality maps onto the scale around it: 80 is this CRF, 100 a quarter of it, 1 the worst (FORMATS.md §5.1)\n\nEnv: XI_VIDEO_ENCODE_VP9_CRF",
              "markdownDescription": "CRF of WebM (VP9, libvpx) output when the request sets no quality (0–63). A request quality maps onto the scale around it: 80 is this CRF, 100 a quarter of it, 1 the worst (FORMATS.md §5.1)\n\nEnv: `XI_VIDEO_ENCODE_VP9_CRF`",
              "title": "XI_VIDEO_ENCODE_VP9_CRF",
              "type": "integer"
            },
            "vp9_speed": {
              "default": 8,
              "description": "libvpx cpu-used in its realtime mode (0–9; higher is faster and larger)\n\nEnv: XI_VIDEO_ENCODE_VP9_SPEED",
              "markdownDescription": "libvpx `cpu-used` in its realtime mode (0–9; higher is faster and larger)\n\nEnv: `XI_VIDEO_ENCODE_VP9_SPEED`",
              "title": "XI_VIDEO_ENCODE_VP9_SPEED",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "max_animation_frames": {
          "default": 100,
          "description": "Frames a video_thumbnail_tile sprite or a video_thumbnail_animation decodes (1–10000); larger grids keep their columns and lose rows, longer animations are cut. XI_LIMITS_MAX_ANIMATION_FRAMES and the pixel limits still apply to the result (422)\n\nEnv: XI_VIDEO_MAX_ANIMATION_FRAMES",
          "markdownDescription": "Frames a `video_thumbnail_tile` sprite or a `video_thumbnail_animation` decodes (1–10000); larger grids keep their columns and lose rows, longer animations are cut. `XI_LIMITS_MAX_ANIMATION_FRAMES` and the pixel limits still apply to the result (422)\n\nEnv: `XI_VIDEO_MAX_ANIMATION_FRAMES`",
          "title": "XI_VIDEO_MAX_ANIMATION_FRAMES",
          "type": "integer"
        },
        "max_bytes": {
          "$ref": "#/$defs/byteSize",
          "default": "100MiB",
          "description": "Largest video source; XI_LIMITS_MAX_SRC_BYTES applies first, at fetch, so raise both for large videos (422)\n\nEnv: XI_VIDEO_MAX_BYTES",
          "markdownDescription": "Largest video source; `XI_LIMITS_MAX_SRC_BYTES` applies first, at fetch, so raise both for large videos (422)\n\nEnv: `XI_VIDEO_MAX_BYTES`",
          "title": "XI_VIDEO_MAX_BYTES"
        },
        "max_duration": {
          "$ref": "#/$defs/duration",
          "default": "1h0m0s",
          "description": "Longest video, from the container header, before any frame is decoded (422)\n\nEnv: XI_VIDEO_MAX_DURATION",
          "markdownDescription": "Longest video, from the container header, before any frame is decoded (422)\n\nEnv: `XI_VIDEO_MAX_DURATION`",
          "title": "XI_VIDEO_MAX_DURATION"
        },
        "max_pixels": {
          "default": 33177600,
          "description": "Width × height of the video stream (8K UHD); the decoder refuses larger frames too (422). XI_LIMITS_MAX_SRC_PIXELS applies to the frame as well\n\nEnv: XI_VIDEO_MAX_PIXELS",
          "markdownDescription": "Width × height of the video stream (8K UHD); the decoder refuses larger frames too (422). `XI_LIMITS_MAX_SRC_PIXELS` applies to the frame as well\n\nEnv: `XI_VIDEO_MAX_PIXELS`",
          "title": "XI_VIDEO_MAX_PIXELS",
          "type": "integer"
        },
        "thumbnail": {
          "additionalProperties": false,
          "properties": {
            "keyframes": {
              "default": false,
              "description": "Take the keyframe at or before the timestamp instead of the exact frame (faster, less precise; imgproxy IMGPROXY_VIDEO_THUMBNAIL_KEYFRAMES); a request's video_thumbnail_keyframes overrides it\n\nEnv: XI_VIDEO_THUMBNAIL_KEYFRAMES",
              "markdownDescription": "Take the keyframe at or before the timestamp instead of the exact frame (faster, less precise; imgproxy `IMGPROXY_VIDEO_THUMBNAIL_KEYFRAMES`); a request's `video_thumbnail_keyframes` overrides it\n\nEnv: `XI_VIDEO_THUMBNAIL_KEYFRAMES`",
              "title": "XI_VIDEO_THUMBNAIL_KEYFRAMES",
              "type": "boolean"
            },
            "samples": {
              "default": 5,
              "description": "Keyframes best-frame selection compares (1–32)\n\nEnv: XI_VIDEO_THUMBNAIL_SAMPLES",
              "markdownDescription": "Keyframes best-frame selection compares (1–32)\n\nEnv: `XI_VIDEO_THUMBNAIL_SAMPLES`",
              "title": "XI_VIDEO_THUMBNAIL_SAMPLES",
              "type": "integer"
            },
            "second": {
              "$ref": "#/$defs/videoSecond",
              "default": "auto",
              "description": "Frame taken when a request sets no video_thumbnail_second: seconds (negative counts from the end; clamped to the video), or auto for best-frame selection (imgproxy IMGPROXY_VIDEO_THUMBNAIL_SECOND, whose default is 1)\n\nEnv: XI_VIDEO_THUMBNAIL_SECOND",
              "markdownDescription": "Frame taken when a request sets no `video_thumbnail_second`: seconds (negative counts from the end; clamped to the video), or `auto` for best-frame selection (imgproxy `IMGPROXY_VIDEO_THUMBNAIL_SECOND`, whose default is 1)\n\nEnv: `XI_VIDEO_THUMBNAIL_SECOND`",
              "title": "XI_VIDEO_THUMBNAIL_SECOND"
            },
            "span": {
              "$ref": "#/$defs/duration",
              "default": "10s",
              "description": "How much of the start of a video best-frame selection samples\n\nEnv: XI_VIDEO_THUMBNAIL_SPAN",
              "markdownDescription": "How much of the start of a video best-frame selection samples\n\nEnv: `XI_VIDEO_THUMBNAIL_SPAN`",
              "title": "XI_VIDEO_THUMBNAIL_SPAN"
            }
          },
          "type": "object"
        },
        "transform": {
          "additionalProperties": false,
          "properties": {
            "abr_ladder": {
              "default": [
                "1080:5000k",
                "720:2800k",
                "480:1400k",
                "360:800k",
                "240:400k"
              ],
              "description": "Renditions of HLS and DASH streams (WP-126, ADR 0012 §6), as <height>[:<bit rate>] items: the short side of the frame (64–4320; 720 is 1280×720 for a 16:9 landscape source and 720×1280 for a portrait one) and its peak video bit rate in bits per second with an optional k/m suffix (left out: the standard rate of that height). 1–8 rungs; rungs larger than the source are dropped (a smaller source gets one rendition of its own size). A URL may name its own subset (Cloudinary sp_, ImageKit sr-, imgproxy st)\n\nEnv: XI_VIDEO_TRANSFORM_ABR_LADDER",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Renditions of HLS and DASH streams (WP-126, ADR 0012 §6), as `<height>[:<bit rate>]` items: the short side of the frame (64–4320; `720` is 1280×720 for a 16:9 landscape source and 720×1280 for a portrait one) and its peak video bit rate in bits per second with an optional `k`/`m` suffix (left out: the standard rate of that height). 1–8 rungs; rungs larger than the source are dropped (a smaller source gets one rendition of its own size). A URL may name its own subset (Cloudinary `sp_`, ImageKit `sr-`, imgproxy `st`)\n\nEnv: `XI_VIDEO_TRANSFORM_ABR_LADDER`",
              "title": "XI_VIDEO_TRANSFORM_ABR_LADDER",
              "type": "array"
            },
            "audio_chunk": {
              "$ref": "#/$defs/duration",
              "default": "30s",
              "description": "How much of a stream's audio one transcode encodes (WP-131), rounded down to whole segments (at least one): a segment's request encodes its chunk, every chunk starts with a pre-roll of the source so the chunks join without gaps or clicks, and the next chunk is prefetched. Only sources whose audio timestamps name samples exactly (MP4, MOV, Ogg, FLAC, WAV, MP3; not Matroska/WebM, which count milliseconds) and streams without loudnorm are chunked; the others encode their track at once. 0 (or whole seconds up to 1h): 0 encodes every track at once (the WP-126 behaviour), which needs XI_VIDEO_TRANSFORM_TIMEOUT to cover the whole track's encode\n\nEnv: XI_VIDEO_TRANSFORM_AUDIO_CHUNK",
              "markdownDescription": "How much of a stream's audio one transcode encodes (WP-131), rounded down to whole segments (at least one): a segment's request encodes its chunk, every chunk starts with a pre-roll of the source so the chunks join without gaps or clicks, and the next chunk is prefetched. Only sources whose audio timestamps name samples exactly (MP4, MOV, Ogg, FLAC, WAV, MP3; not Matroska/WebM, which count milliseconds) and streams without `loudnorm` are chunked; the others encode their track at once. `0` (or whole seconds up to `1h`): `0` encodes every track at once (the WP-126 behaviour), which needs `XI_VIDEO_TRANSFORM_TIMEOUT` to cover the whole track's encode\n\nEnv: `XI_VIDEO_TRANSFORM_AUDIO_CHUNK`",
              "title": "XI_VIDEO_TRANSFORM_AUDIO_CHUNK"
            },
            "capacity": {
              "description": "Cost units the running transcodes may hold together. A transcode's cost is estimated from its probe and plan (decode + encode pixels × frames × a measured per-codec factor, audio seconds; HLS/DASH per segment): one unit is about a millisecond of one core's work, so 10000 is ten seconds of work per worker. A transcode costing more than the whole capacity is not refused: it runs alone\n\nDefault: workers × 10000\n\nEnv: XI_VIDEO_TRANSFORM_CAPACITY",
              "markdownDescription": "Cost units the running transcodes may hold together. A transcode's cost is estimated from its probe and plan (decode + encode pixels × frames × a measured per-codec factor, audio seconds; HLS/DASH per segment): one unit is about a millisecond of one core's work, so `10000` is ten seconds of work per worker. A transcode costing more than the whole capacity is not refused: it runs alone\n\nDefault: workers × 10000\n\nEnv: `XI_VIDEO_TRANSFORM_CAPACITY`",
              "title": "XI_VIDEO_TRANSFORM_CAPACITY",
              "type": "integer"
            },
            "enabled": {
              "default": false,
              "description": "Video transformations (WP-121 to WP-123, ADR 0012): /video/<signature>/<options>/<source>@<container> and /q/video?url=… (URL-DIALECTS §3.8) trim, resize, crop, rotate and transcode a video into MP4 or WebM, its audio (or an audio file's) into M4A, MP3, Opus, Ogg, FLAC or WAV with volume, loudness normalisation and fades, or draw it as a PNG or WebP waveform. Off: they answer 404. true fails startup on an image without FFmpeg (slim); with XI_ROLE=image the pool is not created. Unsigned requests are refused by default (XI_VIDEO_TRANSFORM_UNSIGNED_POLICY). Which codecs exist depends on the image: MP4 is H.264 in gpl and AV1 in full; audio needs the WP-120 FFmpeg\n\nEnv: XI_VIDEO_TRANSFORM_ENABLED",
              "markdownDescription": "Video transformations (WP-121 to WP-123, ADR 0012): `/video/<signature>/<options>/<source>@<container>` and `/q/video?url=…` (URL-DIALECTS §3.8) trim, resize, crop, rotate and transcode a video into MP4 or WebM, its audio (or an audio file's) into M4A, MP3, Opus, Ogg, FLAC or WAV with volume, loudness normalisation and fades, or draw it as a PNG or WebP waveform. Off: they answer 404. `true` fails startup on an image without FFmpeg (`slim`); with `XI_ROLE=image` the pool is not created. Unsigned requests are refused by default (`XI_VIDEO_TRANSFORM_UNSIGNED_POLICY`). Which codecs exist depends on the image: MP4 is H.264 in `gpl` and AV1 in `full`; audio needs the WP-120 FFmpeg\n\nEnv: `XI_VIDEO_TRANSFORM_ENABLED`",
              "title": "XI_VIDEO_TRANSFORM_ENABLED",
              "type": "boolean"
            },
            "max_fps": {
              "default": 60,
              "description": "Highest output frame rate: a faster source is reduced to it, a faster request refused (422) (1–240)\n\nEnv: XI_VIDEO_TRANSFORM_MAX_FPS",
              "markdownDescription": "Highest output frame rate: a faster source is reduced to it, a faster request refused (422) (1–240)\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_FPS`",
              "title": "XI_VIDEO_TRANSFORM_MAX_FPS",
              "type": "number"
            },
            "max_input_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "512MiB",
              "description": "Largest video source of a transformation (422); the fetcher accepts the larger of this and XI_LIMITS_MAX_SRC_BYTES for video requests. A source over XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD is downloaded into a spool file instead of memory\n\nEnv: XI_VIDEO_TRANSFORM_MAX_INPUT_BYTES",
              "markdownDescription": "Largest video source of a transformation (422); the fetcher accepts the larger of this and `XI_LIMITS_MAX_SRC_BYTES` for video requests. A source over `XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD` is downloaded into a spool file instead of memory\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_INPUT_BYTES`",
              "title": "XI_VIDEO_TRANSFORM_MAX_INPUT_BYTES"
            },
            "max_input_duration": {
              "$ref": "#/$defs/duration",
              "default": "1h0m0s",
              "description": "Longest video source, from its header (422)\n\nEnv: XI_VIDEO_TRANSFORM_MAX_INPUT_DURATION",
              "markdownDescription": "Longest video source, from its header (422)\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_INPUT_DURATION`",
              "title": "XI_VIDEO_TRANSFORM_MAX_INPUT_DURATION"
            },
            "max_input_pixels": {
              "default": 33177600,
              "description": "Width × height of the source's video stream (8K UHD); the decoder refuses larger frames too (422)\n\nEnv: XI_VIDEO_TRANSFORM_MAX_INPUT_PIXELS",
              "markdownDescription": "Width × height of the source's video stream (8K UHD); the decoder refuses larger frames too (422)\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_INPUT_PIXELS`",
              "title": "XI_VIDEO_TRANSFORM_MAX_INPUT_PIXELS",
              "type": "integer"
            },
            "max_jobs": {
              "description": "Most transcodes running at once whatever their cost, so cheap ones (audio, small segments) can run beside a heavy one but never more than this (0–4096; 0 is the default)\n\nDefault: 2 × workers\n\nEnv: XI_VIDEO_TRANSFORM_MAX_JOBS",
              "markdownDescription": "Most transcodes running at once whatever their cost, so cheap ones (audio, small segments) can run beside a heavy one but never more than this (0–4096; 0 is the default)\n\nDefault: 2 × workers\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_JOBS`",
              "title": "XI_VIDEO_TRANSFORM_MAX_JOBS",
              "type": "integer"
            },
            "max_output_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "256MiB",
              "description": "Largest output file; the muxer stops past it (422). An output growing past XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD moves into a spool file; while it is written it reserves this much of XI_VIDEO_TRANSFORM_SPOOL_MAX_BYTES\n\nEnv: XI_VIDEO_TRANSFORM_MAX_OUTPUT_BYTES",
              "markdownDescription": "Largest output file; the muxer stops past it (422). An output growing past `XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD` moves into a spool file; while it is written it reserves this much of `XI_VIDEO_TRANSFORM_SPOOL_MAX_BYTES`\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_OUTPUT_BYTES`",
              "title": "XI_VIDEO_TRANSFORM_MAX_OUTPUT_BYTES"
            },
            "max_output_duration": {
              "$ref": "#/$defs/duration",
              "default": "5m0s",
              "description": "Longest output (the cut, or the whole source) (422)\n\nEnv: XI_VIDEO_TRANSFORM_MAX_OUTPUT_DURATION",
              "markdownDescription": "Longest output (the cut, or the whole source) (422)\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_OUTPUT_DURATION`",
              "title": "XI_VIDEO_TRANSFORM_MAX_OUTPUT_DURATION"
            },
            "max_output_pixels": {
              "default": 8294400,
              "description": "Largest output frame, width × height (4K UHD). A request that names no size is scaled down to fit it (aspect kept); an explicit size over it is 422\n\nEnv: XI_VIDEO_TRANSFORM_MAX_OUTPUT_PIXELS",
              "markdownDescription": "Largest output frame, width × height (4K UHD). A request that names no size is scaled down to fit it (aspect kept); an explicit size over it is 422\n\nEnv: `XI_VIDEO_TRANSFORM_MAX_OUTPUT_PIXELS`",
              "title": "XI_VIDEO_TRANSFORM_MAX_OUTPUT_PIXELS",
              "type": "integer"
            },
            "prefetch_segments": {
              "default": 2,
              "description": "Segments after a requested one that are transcoded ahead (0–16), only when the video pool has a free slot and the segment's cost in free capacity at that moment and nobody waits: a prefetch never queues and never delays a request\n\nEnv: XI_VIDEO_TRANSFORM_PREFETCH_SEGMENTS",
              "markdownDescription": "Segments after a requested one that are transcoded ahead (0–16), only when the video pool has a free slot and the segment's cost in free capacity at that moment and nobody waits: a prefetch never queues and never delays a request\n\nEnv: `XI_VIDEO_TRANSFORM_PREFETCH_SEGMENTS`",
              "title": "XI_VIDEO_TRANSFORM_PREFETCH_SEGMENTS",
              "type": "integer"
            },
            "presets": {
              "additionalProperties": {
                "type": "string"
              },
              "description": "Video presets (WP-132): name=options,name2=options, the options in /video syntax (URL-DIALECTS §3.8: /-separated name:args with XI_DIALECTS_IMGPROXY_ARGUMENTS_SEPARATOR, no chained pipelines, no delivery options such as cb, exp or fn); YAML video.transform.presets: {name: options}. URLs use them with pr:name (imgproxy /video, nested references allowed, expanded in place: later options override), preset=name (/q/video), t_name (Cloudinary video) and n-name (ImageKit video), where the presets are the starting point and the URL's own parameters override them. default applies to every video transformation first. Separate from XI_PRESETS (image options). Validated at startup: a bad name or syntax, an unknown option, a bad value or a preset over XI_VIDEO_TRANSFORM_MAX_OUTPUT_PIXELS, _MAX_FPS or _MAX_OUTPUT_DURATION fails it, naming the preset. Names (not contents) are listed in the playground's video meta. With XI_PRESETS_ONLY=true, /video URLs may hold video preset references only\n\nEnv: XI_VIDEO_TRANSFORM_PRESETS",
              "markdownDescription": "Video presets (WP-132): `name=options,name2=options`, the options in `/video` syntax (URL-DIALECTS §3.8: `/`-separated `name:args` with `XI_DIALECTS_IMGPROXY_ARGUMENTS_SEPARATOR`, no chained pipelines, no delivery options such as `cb`, `exp` or `fn`); YAML `video.transform.presets: {name: options}`. URLs use them with `pr:name` (imgproxy `/video`, nested references allowed, expanded in place: later options override), `preset=name` (`/q/video`), `t_name` (Cloudinary video) and `n-name` (ImageKit video), where the presets are the starting point and the URL's own parameters override them. `default` applies to every video transformation first. Separate from `XI_PRESETS` (image options). Validated at startup: a bad name or syntax, an unknown option, a bad value or a preset over `XI_VIDEO_TRANSFORM_MAX_OUTPUT_PIXELS`, `_MAX_FPS` or `_MAX_OUTPUT_DURATION` fails it, naming the preset. Names (not contents) are listed in the playground's video meta. With `XI_PRESETS_ONLY=true`, `/video` URLs may hold video preset references only\n\nEnv: `XI_VIDEO_TRANSFORM_PRESETS`",
              "title": "XI_VIDEO_TRANSFORM_PRESETS",
              "type": "object"
            },
            "queue": {
              "default": 8,
              "description": "Transcodes that may wait beyond XI_VIDEO_TRANSFORM_MAX_JOBS (a place is taken from the probe on); beyond it 429 + Retry-After\n\nEnv: XI_VIDEO_TRANSFORM_QUEUE",
              "markdownDescription": "Transcodes that may wait beyond `XI_VIDEO_TRANSFORM_MAX_JOBS` (a place is taken from the probe on); beyond it 429 + `Retry-After`\n\nEnv: `XI_VIDEO_TRANSFORM_QUEUE`",
              "title": "XI_VIDEO_TRANSFORM_QUEUE",
              "type": "integer"
            },
            "queue_cost": {
              "description": "Cost units the waiting transcodes may add up to; a transcode that would go over it is refused with 429 + Retry-After (one always waits behind an empty queue)\n\nDefault: max(queue, 1) × 10000\n\nEnv: XI_VIDEO_TRANSFORM_QUEUE_COST",
              "markdownDescription": "Cost units the waiting transcodes may add up to; a transcode that would go over it is refused with 429 + `Retry-After` (one always waits behind an empty queue)\n\nDefault: max(queue, 1) × 10000\n\nEnv: `XI_VIDEO_TRANSFORM_QUEUE_COST`",
              "title": "XI_VIDEO_TRANSFORM_QUEUE_COST",
              "type": "integer"
            },
            "queue_timeout": {
              "$ref": "#/$defs/duration",
              "default": "30s",
              "description": "How long a transcode waits for its probe and for capacity before 503 + Retry-After. Waiting is first come, first served: a large transcode at the head of the queue is not overtaken by smaller ones\n\nEnv: XI_VIDEO_TRANSFORM_QUEUE_TIMEOUT",
              "markdownDescription": "How long a transcode waits for its probe and for capacity before 503 + `Retry-After`. Waiting is first come, first served: a large transcode at the head of the queue is not overtaken by smaller ones\n\nEnv: `XI_VIDEO_TRANSFORM_QUEUE_TIMEOUT`",
              "title": "XI_VIDEO_TRANSFORM_QUEUE_TIMEOUT"
            },
            "segment_duration": {
              "$ref": "#/$defs/duration",
              "default": "6s",
              "description": "Length of a stream's segments, whole seconds from 1s to 30s; the last one is shorter. A video segment has one keyframe (its first) unless the request sets a keyframe interval\n\nEnv: XI_VIDEO_TRANSFORM_SEGMENT_DURATION",
              "markdownDescription": "Length of a stream's segments, whole seconds from `1s` to `30s`; the last one is shorter. A video segment has one keyframe (its first) unless the request sets a keyframe interval\n\nEnv: `XI_VIDEO_TRANSFORM_SEGMENT_DURATION`",
              "title": "XI_VIDEO_TRANSFORM_SEGMENT_DURATION"
            },
            "spool_dir": {
              "description": "Directory of the spool files (WP-121b): video sources and outputs larger than the threshold live there instead of memory. Created 0700 if missing; it must be writable, so with a read-only root file system mount a tmpfs or an emptyDir there (DEPLOY.md). Files are unlinked as soon as they are created (an open descriptor is their only name), so they vanish on every path, a crash included; at start, files named xi-spool-* that a previous process left are removed. Absolute path\n\nDefault: temporary directory + /xtandard-video (/tmp/xtandard-video in the images)\n\nEnv: XI_VIDEO_TRANSFORM_SPOOL_DIR",
              "markdownDescription": "Directory of the spool files (WP-121b): video sources and outputs larger than the threshold live there instead of memory. Created `0700` if missing; it must be writable, so with a read-only root file system mount a `tmpfs` or an `emptyDir` there (DEPLOY.md). Files are unlinked as soon as they are created (an open descriptor is their only name), so they vanish on every path, a crash included; at start, files named `xi-spool-*` that a previous process left are removed. Absolute path\n\nDefault: temporary directory + `/xtandard-video` (`/tmp/xtandard-video` in the images)\n\nEnv: `XI_VIDEO_TRANSFORM_SPOOL_DIR`",
              "title": "XI_VIDEO_TRANSFORM_SPOOL_DIR",
              "type": "string"
            },
            "spool_max_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "0",
              "description": "Bound on the bytes of all spool files at once: a download reserves its declared length (or grows while it reads), an encode reserves XI_VIDEO_TRANSFORM_MAX_OUTPUT_BYTES until it ends; a reservation that does not fit, after dropping the shared sources nobody reads, answers 503 with Retry-After: 5. 0 is no bound (the file system's size is). Needs a threshold above 0\n\nEnv: XI_VIDEO_TRANSFORM_SPOOL_MAX_BYTES",
              "markdownDescription": "Bound on the bytes of all spool files at once: a download reserves its declared length (or grows while it reads), an encode reserves `XI_VIDEO_TRANSFORM_MAX_OUTPUT_BYTES` until it ends; a reservation that does not fit, after dropping the shared sources nobody reads, answers 503 with `Retry-After: 5`. `0` is no bound (the file system's size is). Needs a threshold above 0\n\nEnv: `XI_VIDEO_TRANSFORM_SPOOL_MAX_BYTES`",
              "title": "XI_VIDEO_TRANSFORM_SPOOL_MAX_BYTES"
            },
            "spool_threshold": {
              "$ref": "#/$defs/byteSize",
              "default": "32MiB",
              "description": "A video source (declared length, or found while reading one of unknown length) or a transcode output larger than this goes to a spool file instead of memory, so a transcode's memory no longer grows with MAX_INPUT_BYTES + MAX_OUTPUT_BYTES. The file is read and written by the FFmpeg shim through a duplicate of its descriptor, served with Range from the file and streamed into the disk and S3 result-cache tiers (the memory tier skips it; redis keeps only values up to XI_CACHE_RESULT_REDIS_MAX_VALUE_BYTES). Stream segments and waveforms stay in memory. 0 keeps everything in memory (the WP-121 behaviour)\n\nEnv: XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD",
              "markdownDescription": "A video source (declared length, or found while reading one of unknown length) or a transcode output larger than this goes to a spool file instead of memory, so a transcode's memory no longer grows with `MAX_INPUT_BYTES + MAX_OUTPUT_BYTES`. The file is read and written by the FFmpeg shim through a duplicate of its descriptor, served with `Range` from the file and streamed into the disk and S3 result-cache tiers (the memory tier skips it; redis keeps only values up to `XI_CACHE_RESULT_REDIS_MAX_VALUE_BYTES`). Stream segments and waveforms stay in memory. `0` keeps everything in memory (the WP-121 behaviour)\n\nEnv: `XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD`",
              "title": "XI_VIDEO_TRANSFORM_SPOOL_THRESHOLD"
            },
            "spool_ttl": {
              "$ref": "#/$defs/duration",
              "default": "1m0s",
              "description": "A spooled source is shared by the requests that need it (the segments of an HLS or DASH stream, repeated transformations): reused without asking the origin for this long after its download, kept while it is used at least once per period (then revalidated, with If-None-Match/If-Modified-Since when the origin sent an ETag or Last-Modified), and removed after a period without use. 0 shares nothing (concurrent requests still share one download)\n\nEnv: XI_VIDEO_TRANSFORM_SPOOL_TTL",
              "markdownDescription": "A spooled source is shared by the requests that need it (the segments of an HLS or DASH stream, repeated transformations): reused without asking the origin for this long after its download, kept while it is used at least once per period (then revalidated, with `If-None-Match`/`If-Modified-Since` when the origin sent an ETag or Last-Modified), and removed after a period without use. `0` shares nothing (concurrent requests still share one download)\n\nEnv: `XI_VIDEO_TRANSFORM_SPOOL_TTL`",
              "title": "XI_VIDEO_TRANSFORM_SPOOL_TTL"
            },
            "stream_cache_bytes": {
              "$ref": "#/$defs/byteSize",
              "default": "256MiB",
              "description": "In-memory cache of stream probes and segments (prefetched ones included), in front of the result cache, which keeps them too (a persistent tier is recommended for streams). 0 is off: then prefetching only helps with a result cache\n\nEnv: XI_VIDEO_TRANSFORM_STREAM_CACHE_BYTES",
              "markdownDescription": "In-memory cache of stream probes and segments (prefetched ones included), in front of the result cache, which keeps them too (a persistent tier is recommended for streams). `0` is off: then prefetching only helps with a result cache\n\nEnv: `XI_VIDEO_TRANSFORM_STREAM_CACHE_BYTES`",
              "title": "XI_VIDEO_TRANSFORM_STREAM_CACHE_BYTES"
            },
            "stream_keys": {
              "description": "Hex keys (at least 16 bytes) that seal the tickets of child URLs (AES-256-GCM: they cannot be forged and do not show the source); the first seals, every one opens (rotation). Empty derives one from the first XI_SIGNING_KEYS key, else a random key per process (child URLs then work only on the instance that wrote the manifest, until it restarts; a startup warning says so). Set it on every instance of a fleet\n\nEnv: XI_VIDEO_TRANSFORM_STREAM_KEYS (or XI_VIDEO_TRANSFORM_STREAM_KEYS_FILE)",
              "items": {
                "$ref": "#/$defs/hexKey"
              },
              "markdownDescription": "Hex keys (at least 16 bytes) that seal the tickets of child URLs (AES-256-GCM: they cannot be forged and do not show the source); the first seals, every one opens (rotation). Empty derives one from the first `XI_SIGNING_KEYS` key, else a random key per process (child URLs then work only on the instance that wrote the manifest, until it restarts; a startup warning says so). Set it on every instance of a fleet\n\nEnv: `XI_VIDEO_TRANSFORM_STREAM_KEYS` (or `XI_VIDEO_TRANSFORM_STREAM_KEYS_FILE`)",
              "title": "XI_VIDEO_TRANSFORM_STREAM_KEYS",
              "type": "array"
            },
            "stream_ttl": {
              "$ref": "#/$defs/duration",
              "default": "12h0m0s",
              "description": "How long the child URLs of a manifest (/_stream/<ticket>/…: playlists and segments) work; a manifest is cacheable for a quarter of it (its tickets move in quarter steps). At least 1m\n\nEnv: XI_VIDEO_TRANSFORM_STREAM_TTL",
              "markdownDescription": "How long the child URLs of a manifest (`/_stream/<ticket>/…`: playlists and segments) work; a manifest is cacheable for a quarter of it (its tickets move in quarter steps). At least `1m`\n\nEnv: `XI_VIDEO_TRANSFORM_STREAM_TTL`",
              "title": "XI_VIDEO_TRANSFORM_STREAM_TTL"
            },
            "threads": {
              "default": 1,
              "description": "Decoder and encoder threads of one transcode (1–64)\n\nEnv: XI_VIDEO_TRANSFORM_THREADS",
              "markdownDescription": "Decoder and encoder threads of one transcode (1–64)\n\nEnv: `XI_VIDEO_TRANSFORM_THREADS`",
              "title": "XI_VIDEO_TRANSFORM_THREADS",
              "type": "integer"
            },
            "timeout": {
              "$ref": "#/$defs/duration",
              "default": "2m0s",
              "description": "Wall time of one transcode from admission (504). A video request's budget is XI_FETCH_TIMEOUT + queue timeout + this, instead of XI_SERVER_REQUEST_TIMEOUT, and its write deadline is extended to match\n\nEnv: XI_VIDEO_TRANSFORM_TIMEOUT",
              "markdownDescription": "Wall time of one transcode from admission (504). A video request's budget is `XI_FETCH_TIMEOUT` + queue timeout + this, instead of `XI_SERVER_REQUEST_TIMEOUT`, and its write deadline is extended to match\n\nEnv: `XI_VIDEO_TRANSFORM_TIMEOUT`",
              "title": "XI_VIDEO_TRANSFORM_TIMEOUT"
            },
            "unsigned_allowed_options": {
              "description": "Options an unsigned request may set besides its presets under the restricted video policy: any of start, end, duration, mute (by meaning, in every dialect: imgproxy vs, Cloudinary so_, ImageKit so-, …). Empty: none\n\nEnv: XI_VIDEO_TRANSFORM_UNSIGNED_ALLOWED_OPTIONS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Options an unsigned request may set besides its presets under the `restricted` video policy: any of `start`, `end`, `duration`, `mute` (by meaning, in every dialect: imgproxy `vs`, Cloudinary `so_`, ImageKit `so-`, …). Empty: none\n\nEnv: `XI_VIDEO_TRANSFORM_UNSIGNED_ALLOWED_OPTIONS`",
              "title": "XI_VIDEO_TRANSFORM_UNSIGNED_ALLOWED_OPTIONS",
              "type": "array"
            },
            "unsigned_formats": {
              "default": [
                "mp4",
                "webm"
              ],
              "description": "Containers an unsigned restricted request may pick (extension, format, f_, f-) instead of its presets' one, of the same kind: a file with video, audio only (m4a, mp3, opus, ogg, flac, wav) or a waveform picture (png, webp). An automatic choice (Cloudinary f_auto, ImageKit's default) needs mp4 and webm. Empty: the presets' container only\n\nEnv: XI_VIDEO_TRANSFORM_UNSIGNED_FORMATS",
              "items": {
                "type": "string"
              },
              "markdownDescription": "Containers an unsigned `restricted` request may pick (extension, `format`, `f_`, `f-`) instead of its presets' one, of the same kind: a file with video, audio only (`m4a`, `mp3`, `opus`, `ogg`, `flac`, `wav`) or a waveform picture (`png`, `webp`). An automatic choice (Cloudinary `f_auto`, ImageKit's default) needs `mp4` and `webm`. Empty: the presets' container only\n\nEnv: `XI_VIDEO_TRANSFORM_UNSIGNED_FORMATS`",
              "title": "XI_VIDEO_TRANSFORM_UNSIGNED_FORMATS",
              "type": "array"
            },
            "unsigned_max_duration": {
              "$ref": "#/$defs/duration",
              "default": "0s",
              "description": "When an unsigned restricted request changes the cut with start, end or duration, the cut must have a duration or an end and be at most this long (403 otherwise). 0: only XI_VIDEO_TRANSFORM_MAX_OUTPUT_DURATION\n\nEnv: XI_VIDEO_TRANSFORM_UNSIGNED_MAX_DURATION",
              "markdownDescription": "When an unsigned `restricted` request changes the cut with `start`, `end` or `duration`, the cut must have a duration or an end and be at most this long (403 otherwise). `0`: only `XI_VIDEO_TRANSFORM_MAX_OUTPUT_DURATION`\n\nEnv: `XI_VIDEO_TRANSFORM_UNSIGNED_MAX_DURATION`",
              "title": "XI_VIDEO_TRANSFORM_UNSIGNED_MAX_DURATION"
            },
            "unsigned_policy": {
              "default": "deny",
              "description": "Video transformations without a verified signature (ADR 0012 §8), in every dialect: deny answers them 403 even on a mount whose XI_SECURITY_UNSIGNED_POLICY (or dialect policy) is allow; allow leaves them to the mount's policy (a restricted mount still refuses them, its allowlists describe images); restricted (WP-132) admits only what the URL's video presets make of it, on any mount whose policy is not deny: the URL must name at least one of XI_VIDEO_TRANSFORM_PRESETS (imgproxy and query pr/preset, Cloudinary t_, ImageKit n-), and its result must equal what those presets alone give, except for XI_VIDEO_TRANSFORM_UNSIGNED_ALLOWED_OPTIONS and a container of XI_VIDEO_TRANSFORM_UNSIGNED_FORMATS. So HLS/DASH streams and raw:1 come only from presets; no cache buster or cache bypass, security options or chained pipelines. Refusals are 403 unsigned request not allowed (404 with XI_COMPAT_IMGPROXY_STATUS), before anything is fetched. restricted needs at least one video preset. Signed requests are never affected\n\nEnv: XI_VIDEO_TRANSFORM_UNSIGNED_POLICY",
              "enum": [
                "allow",
                "restricted",
                "deny"
              ],
              "markdownDescription": "Video transformations without a verified signature (ADR 0012 §8), in every dialect: `deny` answers them 403 even on a mount whose `XI_SECURITY_UNSIGNED_POLICY` (or dialect policy) is `allow`; `allow` leaves them to the mount's policy (a `restricted` mount still refuses them, its allowlists describe images); `restricted` (WP-132) admits only what the URL's video presets make of it, on any mount whose policy is not `deny`: the URL must name at least one of `XI_VIDEO_TRANSFORM_PRESETS` (imgproxy and query `pr`/`preset`, Cloudinary `t_`, ImageKit `n-`), and its result must equal what those presets alone give, except for `XI_VIDEO_TRANSFORM_UNSIGNED_ALLOWED_OPTIONS` and a container of `XI_VIDEO_TRANSFORM_UNSIGNED_FORMATS`. So HLS/DASH streams and `raw:1` come only from presets; no cache buster or cache bypass, security options or chained pipelines. Refusals are 403 `unsigned request not allowed` (404 with `XI_COMPAT_IMGPROXY_STATUS`), before anything is fetched. `restricted` needs at least one video preset. Signed requests are never affected\n\nEnv: `XI_VIDEO_TRANSFORM_UNSIGNED_POLICY`",
              "title": "XI_VIDEO_TRANSFORM_UNSIGNED_POLICY",
              "type": "string"
            },
            "workers": {
              "description": "Size of the video pool, separate from the image pool (XI_PROCESSING_WORKERS): the probes run at once, and the defaults of XI_VIDEO_TRANSFORM_CAPACITY (WORKERS × 10000) and XI_VIDEO_TRANSFORM_MAX_JOBS (2 × WORKERS). Transcodes are admitted by cost, not one per worker (WP-121c, ADR 0012 §4)\n\nDefault: GOMAXPROCS / 4 (≥ 1)\n\nEnv: XI_VIDEO_TRANSFORM_WORKERS",
              "markdownDescription": "Size of the video pool, separate from the image pool (`XI_PROCESSING_WORKERS`): the probes run at once, and the defaults of `XI_VIDEO_TRANSFORM_CAPACITY` (`WORKERS × 10000`) and `XI_VIDEO_TRANSFORM_MAX_JOBS` (`2 × WORKERS`). Transcodes are admitted by cost, not one per worker (WP-121c, ADR 0012 §4)\n\nDefault: GOMAXPROCS / 4 (≥ 1)\n\nEnv: `XI_VIDEO_TRANSFORM_WORKERS`",
              "title": "XI_VIDEO_TRANSFORM_WORKERS",
              "type": "integer"
            }
          },
          "type": "object"
        }
      },
      "type": "object"
    },
    "vips": {
      "additionalProperties": false,
      "properties": {
        "concurrency": {
          "default": 1,
          "description": "libvips threads per image; change only with benchmarks\n\nEnv: XI_VIPS_CONCURRENCY",
          "markdownDescription": "libvips threads per image; change only with benchmarks\n\nEnv: `XI_VIPS_CONCURRENCY`",
          "title": "XI_VIPS_CONCURRENCY",
          "type": "integer"
        },
        "fail_on": {
          "default": "warning",
          "description": "Env: XI_VIPS_FAIL_ON",
          "enum": [
            "none",
            "truncated",
            "error",
            "warning"
          ],
          "markdownDescription": "Env: `XI_VIPS_FAIL_ON`",
          "title": "XI_VIPS_FAIL_ON",
          "type": "string"
        }
      },
      "type": "object"
    }
  },
  "title": "xtandard-image configuration",
  "type": "object"
}
