{
  "openapi": "3.1.0",
  "info": {
    "title": "SyncWords VOD Express API",
    "version": "1.0.0",
    "description": "Dub, translate and caption recorded video and audio, file by file.\n\nEvery call carries `Authorization: Bearer <token>`, a token created from your SyncWords API\nkey pair with `POST /token/create`. The same keys, and the same tokens, work on the\nSyncWords Live API.\n\nThe flow: create a project with a `config` (`POST /projects`); put media into its input\nfolder, through the API (`POST /projects/{id}/uploads`) or with the AWS CLI and the role\n`PUT /account/aws-access` creates; follow `GET /projects/{id}/files`; download each output\n(`GET /projects/{id}/outputs/download`).\n\nAn error is `{\"message\", \"code\"}`: branch on `code`, which does not change, and show\n`message`. A 422 adds `errors`, every problem keyed by field, or by JSON pointer inside\n`config` (`/config/translations/0/language`).\n\nA project that is valid but will not do what it seems to says so in `meta.messages`:\na translation no output carries, for one, which is still produced and billed. Nothing is\nrefused for it; show the messages beside the project and fix the config when you can.\n\nRequests are rate-limited to 1000 a minute per account. Answers carry `X-RateLimit-Limit`\nand `X-RateLimit-Remaining`; a 429 carries `Retry-After`, in seconds.\n\nA body is JSON or YAML (`Content-Type: application/yaml`), up to 2 MB. An answer is JSON\nunless `Accept` prefers YAML.",
    "contact": {
      "name": "SyncWords support",
      "email": "support@syncwords.com"
    },
    "termsOfService": "https://www.syncwords.com/legal/terms-of-service/"
  },
  "externalDocs": {
    "description": "Guides and examples",
    "url": "/vod-express/"
  },
  "servers": [
    {
      "url": "https://api-vod-express.syncwords.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Your token, your account and its upload role."
    },
    {
      "name": "Schema",
      "description": "What a project’s `config` may contain: builds, the schema, languages and engines."
    },
    {
      "name": "Projects",
      "description": "A project is one `config` and its folders; its processing service works through them."
    },
    {
      "name": "Files",
      "description": "The inputs, their processing state, and the outputs."
    },
    {
      "name": "Feeds",
      "description": "A feed project’s re-published feeds, public for players and aggregators."
    },
    {
      "name": "Uploads",
      "description": "Uploads through the API: S3 multipart uploads this API signs part by part, the bytes sent straight to S3. For browsers, scripts and servers without AWS credentials."
    },
    {
      "name": "Webhooks",
      "description": "Calls to your server when a file is done or failed and when a project stops or fails, instead of polling."
    }
  ],
  "paths": {
    "/token/create": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Create a token",
        "description": "Exchanges your API key pair for the bearer token every other call carries; it needs no token itself. The token belongs to your organization and works on the SyncWords Live API too, as one from the Live API works here. When a token expires, create another: there is no refresh. Not counted against your rate limit.\n\n**Guide:** [Authentication](/vod-express/authentication)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              },
              "example": {
                "username": "YOUR_PUBLIC_KEY",
                "password": "YOUR_SECRET_KEY"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                },
                "example": {
                  "access_token": "482|kJq8vN3mX2pL7rT5wY9zB4cF6hD1sA0gE8uI2oP",
                  "token_type": "Bearer",
                  "expires_in": 1800
                }
              }
            }
          },
          "401": {
            "description": "The key pair matches no account: `invalid_credentials`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Invalid login details",
                  "code": "invalid_credentials"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "`username` or `password` is missing, or not a string: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "The password field is required.",
                  "code": "validation_failed",
                  "errors": {
                    "password": [
                      "The password field is required."
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "The same wrong key pair, sent too often in a minute: `too_many_requests`. Wait the seconds `Retry-After` says.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "createToken"
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Check a token",
        "description": "The account a token belongs to, and whether VOD Express is enabled for it. Answers even when it is not, so a client can tell a disabled account from a bad token.\n\n**Guide:** [Authentication](/vod-express/authentication)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Whoami"
                    }
                  }
                },
                "example": {
                  "data": {
                    "organization": {
                      "id": 141,
                      "name": "Acme Media"
                    },
                    "user": {
                      "id": null,
                      "email": null,
                      "apiUser": true
                    },
                    "vodExpress": {
                      "enabled": true,
                      "defaultBuildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                      "hasAwsAccess": true,
                      "canManageAccount": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getWhoami"
      }
    },
    "/account/aws-access": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get the upload role",
        "description": "The IAM role your account uploads and downloads through with the AWS CLI or an SDK.\n\n**Guide:** [Upload with the AWS CLI](/vod-express/uploads/aws-role)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AwsAccess"
                    }
                  }
                },
                "example": {
                  "data": {
                    "iamPrincipalArn": "arn:aws:iam::444455556666:role/uploader",
                    "roleArn": "arn:aws:iam::710713970991:role/sw-vod-express-vended-upload-141",
                    "externalId": "5f0c1d2e-8a4b-4c6d-9e7f-0a1b2c3d4e5f",
                    "bucket": "sw-vod-express",
                    "region": "us-east-1",
                    "s3Prefix": "s3://sw-vod-express/projects/141/"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No role yet: `aws_access_not_provisioned`. `PUT /account/aws-access` creates it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "AWS access has not been provisioned for this account.",
                  "code": "aws_access_not_provisioned"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getAwsAccess"
      },
      "put": {
        "tags": [
          "Account"
        ],
        "summary": "Create the upload role",
        "description": "Creates your account’s IAM role, trusting the AWS principal you name. Repeating the call with the same principal changes nothing; another principal re-points the same role, and the external id stays. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Upload with the AWS CLI](/vod-express/uploads/aws-role)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "iamPrincipalArn"
                ],
                "additionalProperties": false,
                "properties": {
                  "iamPrincipalArn": {
                    "type": "string",
                    "description": "The AWS identity that will assume the role: a role or user ARN, or a 12-digit account id.",
                    "examples": [
                      "arn:aws:iam::444455556666:role/uploader",
                      "444455556666"
                    ]
                  }
                }
              },
              "example": {
                "iamPrincipalArn": "arn:aws:iam::444455556666:role/uploader"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AwsAccess"
                    }
                  }
                },
                "example": {
                  "data": {
                    "iamPrincipalArn": "arn:aws:iam::444455556666:role/uploader",
                    "roleArn": "arn:aws:iam::710713970991:role/sw-vod-express-vended-upload-141",
                    "externalId": "5f0c1d2e-8a4b-4c6d-9e7f-0a1b2c3d4e5f",
                    "bucket": "sw-vod-express",
                    "region": "us-east-1",
                    "s3Prefix": "s3://sw-vod-express/projects/141/"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its AWS access."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "Not an IAM principal: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "AWS refused a step: `aws_access_failed`. Repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Upload roles are not available in this environment: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createAwsAccess"
      },
      "delete": {
        "tags": [
          "Account"
        ],
        "summary": "Revoke the upload role",
        "description": "Stops the role working: nobody can assume it any more, and credentials already issued through it are refused. `GET /account/aws-access` then answers `404`; a later `PUT` restores the role behind a new external id. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Upload with the AWS CLI](/vod-express/uploads/aws-role)",
        "responses": {
          "204": {
            "description": "Revoked. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its AWS access."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No role to revoke: `aws_access_not_provisioned`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "AWS access has not been provisioned for this account.",
                  "code": "aws_access_not_provisioned"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "AWS refused a step: `aws_access_failed`. Repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Upload roles are not available in this environment: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "revokeAwsAccess"
      }
    },
    "/account/storage-connections": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "List storage connections",
        "description": "Your account’s storage connections — your own S3 or S3-compatible buckets (Cloudflare R2, Wasabi, MinIO), with the keys the service reads and writes them with — oldest first. A project names one in `storage.connectionId` to keep its input and output folders in your bucket. The secret access key is never answered.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StorageConnection"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "01k81e5m3xq0h7wq2c5r8vj2s9",
                      "name": "Newsroom media (R2)",
                      "bucket": "newsroom-media",
                      "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                      "region": null,
                      "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                      "s3Uri": "s3://newsroom-media/",
                      "isDefault": true,
                      "projectsUsing": 2,
                      "lastCheck": {
                        "ok": true,
                        "checkedAt": "2026-10-20T16:02:11.000Z",
                        "error": null
                      },
                      "createdBy": "user 42 <jane@acme.test> via unifiedJwt",
                      "createdAt": "2026-10-20T16:02:11.000Z",
                      "updatedAt": "2026-10-20T16:02:11.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listStorageConnections"
      },
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Add a storage connection",
        "description": "Saves a bucket and its keys for your projects to use. The bucket is listed with the keys first, so a wrong key, bucket or endpoint is refused here (`storage_connection_check_failed`) rather than hours later in processing. Leave `endpoint` out for AWS S3; set it for an S3-compatible store. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StorageConnectionCreate"
              },
              "example": {
                "name": "Newsroom media (R2)",
                "bucket": "newsroom-media",
                "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                "secretAccessKey": "a20dd1248934e758ab890691cc4ab70c46cb696252efcd82ab2ba7342bf9eb13"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorageConnection"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01k81e5m3xq0h7wq2c5r8vj2s9",
                    "name": "Newsroom media (R2)",
                    "bucket": "newsroom-media",
                    "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                    "region": null,
                    "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                    "s3Uri": "s3://newsroom-media/",
                    "isDefault": true,
                    "projectsUsing": 2,
                    "lastCheck": {
                      "ok": true,
                      "checkedAt": "2026-10-20T16:02:11.000Z",
                      "error": null
                    },
                    "createdBy": "user 42 <jane@acme.test> via unifiedJwt",
                    "createdAt": "2026-10-20T16:02:11.000Z",
                    "updatedAt": "2026-10-20T16:02:11.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its storage connections."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "A field is invalid (`validation_failed`), the name is taken, or the bucket could not be listed with the keys (`storage_connection_check_failed`, the store’s own words in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The bucket could not be listed with these keys: InvalidAccessKeyId: The AWS Access Key Id you provided does not exist in our records."
                  ],
                  "code": "storage_connection_check_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "Storage connections are not available in this environment: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createStorageConnection"
      }
    },
    "/account/storage-connections/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The connection’s id."
        }
      ],
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get a storage connection",
        "description": "One storage connection of your account.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorageConnection"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01k81e5m3xq0h7wq2c5r8vj2s9",
                    "name": "Newsroom media (R2)",
                    "bucket": "newsroom-media",
                    "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                    "region": null,
                    "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                    "s3Uri": "s3://newsroom-media/",
                    "isDefault": true,
                    "projectsUsing": 2,
                    "lastCheck": {
                      "ok": true,
                      "checkedAt": "2026-10-20T16:02:11.000Z",
                      "error": null
                    },
                    "createdBy": "user 42 <jane@acme.test> via unifiedJwt",
                    "createdAt": "2026-10-20T16:02:11.000Z",
                    "updatedAt": "2026-10-20T16:02:11.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such storage connection in your account: `storage_connection_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Storage connection not found.",
                  "code": "storage_connection_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getStorageConnection"
      },
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Change a storage connection",
        "description": "Any of the fields of a create. A new bucket, endpoint, region or key is checked as on create, and the running projects on the connection are restarted with it: a running project holds the keys it started with. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StorageConnectionUpdate"
              },
              "example": {
                "secretAccessKey": "a20dd1248934e758ab890691cc4ab70c46cb696252efcd82ab2ba7342bf9eb13"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorageConnection"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01k81e5m3xq0h7wq2c5r8vj2s9",
                    "name": "Newsroom media (R2)",
                    "bucket": "newsroom-media",
                    "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                    "region": null,
                    "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                    "s3Uri": "s3://newsroom-media/",
                    "isDefault": true,
                    "projectsUsing": 2,
                    "lastCheck": {
                      "ok": true,
                      "checkedAt": "2026-10-20T16:02:11.000Z",
                      "error": null
                    },
                    "createdBy": "user 42 <jane@acme.test> via unifiedJwt",
                    "createdAt": "2026-10-20T16:02:11.000Z",
                    "updatedAt": "2026-10-20T16:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "An empty body: `bad_request`. Send at least one field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its storage connections."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such storage connection in your account: `storage_connection_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Storage connection not found.",
                  "code": "storage_connection_not_found"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "A field is invalid (`validation_failed`), the name is taken, or the bucket could not be listed with the keys (`storage_connection_check_failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "Storage connections are not available in this environment: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateStorageConnection"
      },
      "delete": {
        "tags": [
          "Account"
        ],
        "summary": "Delete a storage connection",
        "description": "Forgets the bucket and its keys. Refused while a project of your account uses the connection (`storage_connection_in_use`): move the project to other storage first. Nothing in your bucket is touched. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "responses": {
          "204": {
            "description": "Deleted. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its storage connections."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such storage connection in your account: `storage_connection_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Storage connection not found.",
                  "code": "storage_connection_not_found"
                }
              }
            }
          },
          "409": {
            "description": "A project uses the connection: `storage_connection_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "This storage connection is used by a project; move the project to other storage first."
                  ],
                  "code": "storage_connection_in_use"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "deleteStorageConnection"
      }
    },
    "/account/storage-connections/{id}/check": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The connection’s id."
        }
      ],
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Check a storage connection",
        "description": "Lists the bucket with the stored keys again, and answers the connection with the outcome in `lastCheck` — `200` whether it worked or not. What to call after changing a key or a bucket policy on your side.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorageConnection"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01k81e5m3xq0h7wq2c5r8vj2s9",
                    "name": "Newsroom media (R2)",
                    "bucket": "newsroom-media",
                    "endpoint": "https://c4b5023c93121161674870cf69a92519.r2.cloudflarestorage.com",
                    "region": null,
                    "accessKeyId": "c643baba59d6b976a27dd97a064a06fd",
                    "s3Uri": "s3://newsroom-media/",
                    "isDefault": true,
                    "projectsUsing": 2,
                    "lastCheck": {
                      "ok": true,
                      "checkedAt": "2026-10-20T16:02:11.000Z",
                      "error": null
                    },
                    "createdBy": "user 42 <jane@acme.test> via unifiedJwt",
                    "createdAt": "2026-10-20T16:02:11.000Z",
                    "updatedAt": "2026-10-20T16:02:11.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such storage connection in your account: `storage_connection_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Storage connection not found.",
                  "code": "storage_connection_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "checkStorageConnection"
      }
    },
    "/account/settings": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get the account settings",
        "description": "Your account’s defaults: the storage connection a project created without `storage` uses, or null for SyncWords’ storage.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountSettings"
                    }
                  }
                },
                "example": {
                  "data": {
                    "defaultStorageConnectionId": "01k81e5m3xq0h7wq2c5r8vj2s9"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getAccountSettings"
      },
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Change the account settings",
        "description": "`{defaultStorageConnectionId}`: one of your storage connections, or null to return to SyncWords’ storage. Existing projects are not moved. Only an administrator of your organization, or its API key, may call it.\n\n**Guide:** [Your own bucket](/vod-express/uploads/own-bucket)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountSettings"
              },
              "example": {
                "defaultStorageConnectionId": "01k81e5m3xq0h7wq2c5r8vj2s9"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AccountSettings"
                    }
                  }
                },
                "example": {
                  "data": {
                    "defaultStorageConnectionId": "01k81e5m3xq0h7wq2c5r8vj2s9"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "Not available to the account (`vod_express_not_enabled`, `account_restricted`), or a user who is not an administrator of the organization (`forbidden`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Only an administrator of the organization, or its API key, may change its storage connections."
                  ],
                  "code": "forbidden"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "Not one of your storage connections: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "updateAccountSettings"
      }
    },
    "/build-versions": {
      "get": {
        "tags": [
          "Schema"
        ],
        "summary": "List builds",
        "description": "The processing builds, newest first, and the one a new project of yours gets. A project keeps its build until you change `buildVersion`.\n\n**Guide:** [The project config](/vod-express/configuration/overview)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "defaultBuildVersion": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "0.9.0-alpha1-2026-09-29T15-40Z-99cdd3"
                  ],
                  "meta": {
                    "defaultBuildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "The list of builds cannot be fetched right now (`build_list_unavailable`); repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listBuildVersions"
      }
    },
    "/schema": {
      "get": {
        "tags": [
          "Schema"
        ],
        "summary": "Get the config schema",
        "description": "The JSON Schema (draft-07) of a project’s `config` on one build: every field, its type, allowed values and default. `Accept: application/yaml` for YAML. Validate a config against it before sending it, or render a form from it.\n\n**Guide:** [Config reference](/vod-express/configuration/reference)",
        "parameters": [
          {
            "name": "buildVersion",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "A build from `GET /build-versions`. Left out, your account’s default build.",
            "example": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa"
          }
        ],
        "responses": {
          "200": {
            "description": "A draft-07 JSON Schema (abridged here).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "$schema": "http://json-schema.org/draft-07/schema#",
                  "$id": "https://api-vod-express.syncwords.com/v1/schema?buildVersion=0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                  "type": "object",
                  "additionalProperties": false,
                  "title": "Dubbing Client Config",
                  "required": [
                    "transcription",
                    "outputs"
                  ],
                  "properties": {
                    "sourceMedia": {
                      "title": "Source Media",
                      "oneOf": [
                        "… the input folder, or a Media RSS feed"
                      ]
                    },
                    "transcription": {
                      "title": "Transcription",
                      "oneOf": [
                        "… one schema per engine"
                      ]
                    },
                    "translations": {
                      "title": "Translations",
                      "anyOf": [
                        "… null, or an array of translations"
                      ]
                    },
                    "outputs": {
                      "title": "Outputs",
                      "type": "array",
                      "items": "… the output folder, or a feed"
                    }
                  },
                  "definitions": {
                    "…": "some 40 definitions: sources, engines, voices, outputs, items, tracks"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "422": {
            "description": "A build that is not listed (`validation_failed`), or one that publishes no schema (`build_has_no_schema`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "The list of builds or the build’s schema cannot be fetched right now (`build_list_unavailable`); repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getConfigSchema"
      }
    },
    "/enums/languages": {
      "get": {
        "tags": [
          "Schema"
        ],
        "summary": "List languages",
        "description": "Every language code, with the engines that transcribe it (it can be the spoken language) and translate into it (it can be a target). A language with no transcription engine can only be a target.\n\n**Guide:** [Languages](/vod-express/configuration/languages)",
        "responses": {
          "200": {
            "description": "OK (three of the languages shown)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "description": "What `language` fields take."
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "transcriptionEngines": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "The `transcription.@type`s that recognise speech in it."
                          },
                          "translationEngines": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "The `translations[].engine.@type`s that translate into it."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "code": "en",
                      "name": "English",
                      "transcriptionEngines": [
                        "Speechmatics",
                        "Soniox",
                        "Sarvam"
                      ],
                      "translationEngines": [
                        "Google",
                        "DeepL",
                        "OpenAI",
                        "Nova",
                        "Gemini"
                      ]
                    },
                    {
                      "code": "es",
                      "name": "Spanish",
                      "transcriptionEngines": [
                        "Speechmatics",
                        "Soniox"
                      ],
                      "translationEngines": [
                        "Google",
                        "DeepL",
                        "OpenAI",
                        "Nova",
                        "Gemini"
                      ]
                    },
                    {
                      "code": "pt-BR",
                      "name": "Portuguese (Brazil)",
                      "transcriptionEngines": [],
                      "translationEngines": [
                        "Google",
                        "DeepL",
                        "OpenAI",
                        "Nova",
                        "Gemini"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listLanguages"
      }
    },
    "/voices": {
      "get": {
        "tags": [
          "Schema"
        ],
        "summary": "List known voices",
        "description": "Known Amazon Polly, Azure and Google voices that speak a language, in every dialect of it, plus the Azure voices that speak many languages. This is a list to choose from, not each provider’s whole catalogue: a voice missing here is still accepted, and the provider’s own list has the rest. Deepdub and Sarvam clone the speaker, and MultiVoice chooses its voices for the language, so they have no list.\n\n**Guide:** [Choosing a voice](/vod-express/configuration/voices)",
        "parameters": [
          {
            "name": "language",
            "in": "query",
            "required": true,
            "description": "A language code, as a translation’s `language` takes it: `es` lists every Spanish voice.",
            "schema": {
              "type": "string"
            },
            "example": "es"
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "description": "One provider’s voices only.",
            "schema": {
              "type": "string",
              "enum": [
                "Amazon",
                "Azure",
                "Google"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "provider",
                          "voice",
                          "name",
                          "nativeName",
                          "language",
                          "gender",
                          "engines",
                          "multilingual"
                        ],
                        "properties": {
                          "provider": {
                            "type": "string",
                            "enum": [
                              "Amazon",
                              "Azure",
                              "Google"
                            ],
                            "description": "The `provider.@type` the voice belongs to."
                          },
                          "voice": {
                            "type": "string",
                            "description": "What `provider.voice` takes."
                          },
                          "name": {
                            "type": "string"
                          },
                          "nativeName": {
                            "type": "string",
                            "description": "The name in the voice’s language."
                          },
                          "language": {
                            "type": "string",
                            "description": "The language the provider lists the voice under."
                          },
                          "gender": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "male",
                              "female",
                              null
                            ]
                          },
                          "engines": {
                            "type": [
                              "array",
                              "null"
                            ],
                            "items": {
                              "type": "string",
                              "enum": [
                                "STANDARD",
                                "NEURAL"
                              ]
                            },
                            "description": "Amazon only: the `provider.engine` values the voice has. Null for the others."
                          },
                          "multilingual": {
                            "type": "boolean",
                            "description": "Speaks many languages, not only `language`. Check the provider’s list for which."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "provider": "Azure",
                      "voice": "es-ES-ElviraNeural",
                      "name": "Elvira",
                      "nativeName": "Elvira",
                      "language": "es-ES",
                      "gender": "female",
                      "engines": null,
                      "multilingual": false
                    },
                    {
                      "provider": "Amazon",
                      "voice": "Lucia",
                      "name": "Lucia",
                      "nativeName": "Lucia",
                      "language": "es-ES",
                      "gender": "female",
                      "engines": [
                        "NEURAL",
                        "STANDARD"
                      ],
                      "multilingual": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "422": {
            "description": "No `language`, an unknown one, or a `provider` without a list: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listVoices"
      }
    },
    "/enums/{name}": {
      "get": {
        "tags": [
          "Schema"
        ],
        "summary": "List engines, voice providers and output kinds",
        "description": "The `@type` values one part of the config takes on a build, with their titles.\n\n**Guide:** [Config reference](/vod-express/configuration/reference)",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "Which choice: `source-types` (`sourceMedia.@type`), `destination-types` (`outputs[].@type`), `transcription-types` (`transcription.@type`), `translation-engines` (`translations[].engine.@type`), `tts-engines` (`translations[].ttsOptions.provider.@type`), `output-types` (`outputs[].items[].@type`).",
            "schema": {
              "enum": [
                "source-types",
                "destination-types",
                "transcription-types",
                "translation-engines",
                "tts-engines",
                "output-types"
              ]
            },
            "example": "tts-engines"
          },
          {
            "name": "buildVersion",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "A build from `GET /build-versions`. Left out, your account’s default build.",
            "example": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa"
          }
        ],
        "responses": {
          "200": {
            "description": "OK (`tts-engines`)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EnumValue"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "buildVersion": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "type": "Amazon",
                      "title": "Amazon Polly",
                      "description": null
                    },
                    {
                      "type": "Azure",
                      "title": "Azure",
                      "description": null
                    },
                    {
                      "type": "Deepdub",
                      "title": "Deepdub Text-to-Speech",
                      "description": null
                    },
                    {
                      "type": "Google",
                      "title": "Google Text-to-Speech",
                      "description": null
                    },
                    {
                      "type": "MultiVoice",
                      "title": "Multi-voice",
                      "description": null
                    },
                    {
                      "type": "Sarvam",
                      "title": "Sarvam Voice Cloning",
                      "description": null
                    }
                  ],
                  "meta": {
                    "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such list: `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listEnumValues"
      }
    },
    "/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Your account’s projects, newest first, a page at a time. Follow `links.next` until it is null. To find one project before creating it, filter by its exact `name`.\n\n**Guide:** [Projects](/vod-express/concepts/projects)",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "The page, from 1; below 1 is a 422.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "perPage",
            "in": "query",
            "description": "Projects a page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Only the projects named exactly this, case included. Use it to find a project before creating it again.",
            "schema": {
              "type": "string"
            },
            "example": "Fishing Channel S01"
          },
          {
            "name": "search",
            "in": "query",
            "description": "Only the projects whose name contains this, ignoring case.",
            "schema": {
              "type": "string"
            },
            "example": "fishing"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only the projects in this state: `running` (its processing service is up), `stopped`, or `error` (its last start failed, or its service stopped working); or by what their files reached, as `files` last counted them: `completed` — stopped, nothing left to do (every file `done` or `failed`) and at least one `done`; `failed` — in `error`, or stopped with nothing left to do and no file `done`.",
            "schema": {
              "enum": [
                "running",
                "stopped",
                "error",
                "completed",
                "failed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "01m3k8jnrdw509457nhz7w81bq",
                      "name": "Fishing Channel S01",
                      "description": null,
                      "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                      "status": "running",
                      "files": {
                        "total": 3,
                        "pending": 1,
                        "processing": 0,
                        "done": 1,
                        "failed": 1,
                        "countedAt": "2026-09-28T06:15:00.000Z"
                      },
                      "startOnNewFiles": true,
                      "config": {
                        "sourceMedia": {
                          "@type": "S3FolderSource"
                        },
                        "transcription": {
                          "@type": "Speechmatics",
                          "language": "en"
                        },
                        "translations": [
                          {
                            "language": "es",
                            "engine": {
                              "@type": "Gemini"
                            },
                            "ttsOptions": {
                              "provider": {
                                "@type": "Deepdub",
                                "voice": "auto"
                              }
                            }
                          }
                        ],
                        "outputs": [
                          {
                            "@type": "S3FolderOutput",
                            "items": [
                              {
                                "@type": "VideoFile",
                                "ext": "mp4",
                                "nameTemplate": "${name}.es.${ext}",
                                "useOriginalTracks": true,
                                "dubTracks": [
                                  {
                                    "language": "es",
                                    "title": "Spanish"
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      },
                      "storage": {
                        "connection": null,
                        "path": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/",
                        "inputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/input/",
                        "outputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/output/",
                        "inputPrefix": null,
                        "outputPrefix": null
                      },
                      "meta": {
                        "createdAt": "2026-09-28T05:42:35.534Z",
                        "updatedAt": "2026-09-28T05:42:35.534Z",
                        "createdBy": {
                          "accountId": 141,
                          "accountName": "Acme Media",
                          "userId": null,
                          "userEmail": null,
                          "userFullName": null
                        },
                        "configLocked": false,
                        "messages": []
                      }
                    }
                  ],
                  "links": {
                    "first": "https://api-vod-express.syncwords.com/v1/projects?status=running&page=1",
                    "last": "https://api-vod-express.syncwords.com/v1/projects?status=running&page=1",
                    "prev": null,
                    "next": null
                  },
                  "meta": {
                    "current_page": 1,
                    "from": 1,
                    "last_page": 1,
                    "links": [
                      {
                        "url": null,
                        "label": "&laquo; Previous",
                        "active": false
                      },
                      {
                        "url": "https://api-vod-express.syncwords.com/v1/projects?status=running&page=1",
                        "label": "1",
                        "active": true
                      },
                      {
                        "url": null,
                        "label": "Next &raquo;",
                        "active": false
                      }
                    ],
                    "path": "https://api-vod-express.syncwords.com/v1/projects",
                    "per_page": 25,
                    "to": 1,
                    "total": 1
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "422": {
            "description": "A bad filter: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "The selected status is invalid.",
                  "errors": {
                    "status": [
                      "The selected status is invalid."
                    ]
                  },
                  "code": "validation_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listProjects"
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "description": "Creates a project and starts its processing service, unless `options.autoStart` is false. The config is checked in full first, and every problem is answered at once. Also accepted as YAML (`Content-Type: application/yaml`).\n\nThe config’s `sourceMedia` says where the media comes from: the project’s input folder (`S3FolderSource`, also what a config without one gets), an RSS feed (`RssSource`, the media in each item’s `<enclosure>`) or a Media RSS feed (`MrssSource`, each item’s `<media:content>`). Its `outputs` say where the results go: the output folder (`S3FolderOutput`) and, for a feed project, re-published feeds (`RssOutput` or `MrssOutput`, served at `GET /feeds/{id}/{language}/feed.xml`).\n\nEach call creates a project, even with the same name. To repeat a create safely after a timeout or a dropped connection, send an `Idempotency-Key`: a repeat with the same key and body answers what the first call did, with `Idempotency-Replayed: true`, and creates nothing. Or look the name up with `GET /projects?name=` before creating it again. If the config is valid but the service does not start, the project is kept in `error` and the 422 carries its `id`: call `POST /projects/{id}/start`, not create.\n\n**Guide:** [The project config](/vod-express/configuration/overview)",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Any string of your choice, unique per project you mean to create: a UUID is a good one. A repeat of the call with the same key and body answers what the first call did and creates nothing. The same key with another body is refused (`idempotency_key_reused`). Keys belong to your account and are kept for 24 hours. A key whose call created nothing (a `4xx` other than `service_start_failed`, or a `5xx`) can be used again.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "pattern": "^[!-~]+$"
            },
            "example": "5f0c6f1e-4a8e-4a51-9d0b-3c1d2b7e8a90"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreate"
              },
              "example": {
                "name": "Fishing Channel S01",
                "config": {
                  "sourceMedia": {
                    "@type": "S3FolderSource"
                  },
                  "transcription": {
                    "@type": "Speechmatics",
                    "language": "en"
                  },
                  "translations": [
                    {
                      "language": "es",
                      "engine": {
                        "@type": "Gemini"
                      },
                      "ttsOptions": {
                        "provider": {
                          "@type": "Deepdub",
                          "voice": "auto"
                        }
                      }
                    }
                  ],
                  "outputs": [
                    {
                      "@type": "S3FolderOutput",
                      "items": [
                        {
                          "@type": "VideoFile",
                          "ext": "mp4",
                          "nameTemplate": "${name}.es.${ext}",
                          "useOriginalTracks": true,
                          "dubTracks": [
                            {
                              "language": "es",
                              "title": "Spanish"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            },
            "application/yaml": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreate"
              },
              "example": "name: Fishing Channel S01\nconfig:\n  sourceMedia: { '@type': S3FolderSource }\n  transcription: { '@type': Speechmatics, language: en }\n  translations:\n    - language: es\n      engine: { '@type': Gemini }\n      ttsOptions:\n        provider: { '@type': Deepdub, voice: auto }\n  outputs:\n    - '@type': S3FolderOutput\n      items:\n        - '@type': VideoFile\n          ext: mp4\n          nameTemplate: '${name}.es.${ext}'\n          useOriginalTracks: true\n          dubTracks:\n            - { language: es, title: Spanish }\n"
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "Idempotency-Replayed": {
                "description": "`true` when this is the stored answer of an earlier call with the same `Idempotency-Key`. The project is shown as it was then; `GET /projects/{id}` reads it now.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01m3k8jnrdw509457nhz7w81bq",
                    "name": "Fishing Channel S01",
                    "description": null,
                    "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "status": "running",
                    "files": {
                      "total": 3,
                      "pending": 1,
                      "processing": 0,
                      "done": 1,
                      "failed": 1,
                      "countedAt": "2026-09-28T06:15:00.000Z"
                    },
                    "startOnNewFiles": true,
                    "config": {
                      "sourceMedia": {
                        "@type": "S3FolderSource"
                      },
                      "transcription": {
                        "@type": "Speechmatics",
                        "language": "en"
                      },
                      "translations": [
                        {
                          "language": "es",
                          "engine": {
                            "@type": "Gemini"
                          },
                          "ttsOptions": {
                            "provider": {
                              "@type": "Deepdub",
                              "voice": "auto"
                            }
                          }
                        }
                      ],
                      "outputs": [
                        {
                          "@type": "S3FolderOutput",
                          "items": [
                            {
                              "@type": "VideoFile",
                              "ext": "mp4",
                              "nameTemplate": "${name}.es.${ext}",
                              "useOriginalTracks": true,
                              "dubTracks": [
                                {
                                  "language": "es",
                                  "title": "Spanish"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    "storage": {
                      "connection": null,
                      "path": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/",
                      "inputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/input/",
                      "outputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/output/",
                      "inputPrefix": null,
                      "outputPrefix": null
                    },
                    "meta": {
                      "createdAt": "2026-09-28T05:42:35.534Z",
                      "updatedAt": "2026-09-28T05:42:35.534Z",
                      "createdBy": {
                        "accountId": 141,
                        "accountName": "Acme Media",
                        "userId": null,
                        "userEmail": null,
                        "userFullName": null
                      },
                      "configLocked": false,
                      "messages": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`Idempotency-Key` is not 1 to 255 visible ASCII characters: `invalid_idempotency_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "409": {
            "description": "The first call with this `Idempotency-Key` has not finished yet: `idempotency_in_progress`. Repeat the call after `Retry-After` seconds to get its answer.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before repeating the call.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "A request with this Idempotency-Key is still in progress. Retry it shortly."
                  ],
                  "code": "idempotency_in_progress"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "The request is invalid (`validation_failed`), every problem keyed by its place — a JSON pointer inside `config`. Or the config is valid and the service did not start (`service_start_failed`); the body then carries the project’s `id`. Or the `Idempotency-Key` was used with another body (`idempotency_key_reused`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "validation_failed",
                  "message": "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio. (and 2 more errors)",
                  "errors": {
                    "/config/outputs/0/items/0/dubTracks/0/language": [
                      "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio."
                    ],
                    "/config/outputs/0/items/0/nameTemplate": [
                      "The name template must contain ${name}, or every input would write the same file."
                    ],
                    "/config/translations/0/language": [
                      "Language 'xx' is not supported by engine 'DeepL'"
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "createProject"
      }
    },
    "/projects/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get a project",
        "description": "One project of your account.\n\n**Guide:** [Projects](/vod-express/concepts/projects)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01m3k8jnrdw509457nhz7w81bq",
                    "name": "Fishing Channel S01",
                    "description": null,
                    "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "status": "running",
                    "files": {
                      "total": 3,
                      "pending": 1,
                      "processing": 0,
                      "done": 1,
                      "failed": 1,
                      "countedAt": "2026-09-28T06:15:00.000Z"
                    },
                    "startOnNewFiles": true,
                    "config": {
                      "sourceMedia": {
                        "@type": "S3FolderSource"
                      },
                      "transcription": {
                        "@type": "Speechmatics",
                        "language": "en"
                      },
                      "translations": [
                        {
                          "language": "es",
                          "engine": {
                            "@type": "Gemini"
                          },
                          "ttsOptions": {
                            "provider": {
                              "@type": "Deepdub",
                              "voice": "auto"
                            }
                          }
                        }
                      ],
                      "outputs": [
                        {
                          "@type": "S3FolderOutput",
                          "items": [
                            {
                              "@type": "VideoFile",
                              "ext": "mp4",
                              "nameTemplate": "${name}.es.${ext}",
                              "useOriginalTracks": true,
                              "dubTracks": [
                                {
                                  "language": "es",
                                  "title": "Spanish"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    "storage": {
                      "connection": null,
                      "path": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/",
                      "inputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/input/",
                      "outputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/output/",
                      "inputPrefix": null,
                      "outputPrefix": null
                    },
                    "meta": {
                      "createdAt": "2026-09-28T05:42:35.534Z",
                      "updatedAt": "2026-09-28T05:42:35.534Z",
                      "createdBy": {
                        "accountId": 141,
                        "accountName": "Acme Media",
                        "userId": null,
                        "userEmail": null,
                        "userFullName": null
                      },
                      "configLocked": false,
                      "messages": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getProject"
      },
      "put": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project (PUT)",
        "description": "The same as `PATCH`: every field is optional, and a field sent replaces the stored one. A `422` with `service_start_failed` means the change was stored and the restarted service did not start.\n\n**Guide:** [Projects](/vod-express/concepts/projects)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdate"
              },
              "example": {
                "description": "Season 1, dubbed into Spanish"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01m3k8jnrdw509457nhz7w81bq",
                    "name": "Fishing Channel S01",
                    "description": null,
                    "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "status": "running",
                    "files": {
                      "total": 3,
                      "pending": 1,
                      "processing": 0,
                      "done": 1,
                      "failed": 1,
                      "countedAt": "2026-09-28T06:15:00.000Z"
                    },
                    "startOnNewFiles": true,
                    "config": {
                      "sourceMedia": {
                        "@type": "S3FolderSource"
                      },
                      "transcription": {
                        "@type": "Speechmatics",
                        "language": "en"
                      },
                      "translations": [
                        {
                          "language": "es",
                          "engine": {
                            "@type": "Gemini"
                          },
                          "ttsOptions": {
                            "provider": {
                              "@type": "Deepdub",
                              "voice": "auto"
                            }
                          }
                        }
                      ],
                      "outputs": [
                        {
                          "@type": "S3FolderOutput",
                          "items": [
                            {
                              "@type": "VideoFile",
                              "ext": "mp4",
                              "nameTemplate": "${name}.es.${ext}",
                              "useOriginalTracks": true,
                              "dubTracks": [
                                {
                                  "language": "es",
                                  "title": "Spanish"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    "storage": {
                      "connection": null,
                      "path": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/",
                      "inputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/input/",
                      "outputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/output/",
                      "inputPrefix": null,
                      "outputPrefix": null
                    },
                    "meta": {
                      "createdAt": "2026-09-28T05:42:35.534Z",
                      "updatedAt": "2026-09-28T05:42:35.534Z",
                      "createdBy": {
                        "accountId": 141,
                        "accountName": "Acme Media",
                        "userId": null,
                        "userEmail": null,
                        "userFullName": null
                      },
                      "configLocked": false,
                      "messages": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "409": {
            "description": "SyncWords support manages the project’s configuration for now (`meta.configLocked` is `true`), and the call would change `config` or `buildVersion`: `project_config_locked`. Nothing is stored. `name` and `description` can still be changed, and a `config` equal to the stored one is accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The project's configuration is managed by SyncWords support and cannot be changed right now."
                  ],
                  "code": "project_config_locked"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (`validation_failed`): nothing is stored. Or the change was stored and the restarted service did not start (`service_start_failed`): the body carries the project’s `id`, the project reads `error`, and `POST …/start` retries it with the new config.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "validation_failed",
                  "message": "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio. (and 2 more errors)",
                  "errors": {
                    "/config/outputs/0/items/0/dubTracks/0/language": [
                      "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio."
                    ],
                    "/config/outputs/0/items/0/nameTemplate": [
                      "The name template must contain ${name}, or every input would write the same file."
                    ],
                    "/config/translations/0/language": [
                      "Language 'xx' is not supported by engine 'DeepL'"
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "The running processing service could not be stopped: `service_stop_failed`. Nothing was changed; repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Failed to stop the running processing service"
                  ],
                  "code": "service_stop_failed"
                }
              }
            }
          }
        },
        "operationId": "replaceProject"
      },
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project",
        "description": "Every field is optional, and a field sent replaces the stored one: to change one output, send the whole `config`. Changing `config` or `buildVersion` of a running project restarts its processing service with the new instructions; files already done are not processed again. If the running service cannot be stopped, the answer is 502 and nothing is stored. If the restarted service does not start, the change **is** stored and the answer is 422 `service_start_failed`: the project reads `error` until `POST …/start` succeeds.\n\n**Guide:** [Projects](/vod-express/concepts/projects)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectUpdate"
              },
              "example": {
                "name": "Fishing Channel S01 — Spanish"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01m3k8jnrdw509457nhz7w81bq",
                    "name": "Fishing Channel S01",
                    "description": null,
                    "buildVersion": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "status": "running",
                    "files": {
                      "total": 3,
                      "pending": 1,
                      "processing": 0,
                      "done": 1,
                      "failed": 1,
                      "countedAt": "2026-09-28T06:15:00.000Z"
                    },
                    "startOnNewFiles": true,
                    "config": {
                      "sourceMedia": {
                        "@type": "S3FolderSource"
                      },
                      "transcription": {
                        "@type": "Speechmatics",
                        "language": "en"
                      },
                      "translations": [
                        {
                          "language": "es",
                          "engine": {
                            "@type": "Gemini"
                          },
                          "ttsOptions": {
                            "provider": {
                              "@type": "Deepdub",
                              "voice": "auto"
                            }
                          }
                        }
                      ],
                      "outputs": [
                        {
                          "@type": "S3FolderOutput",
                          "items": [
                            {
                              "@type": "VideoFile",
                              "ext": "mp4",
                              "nameTemplate": "${name}.es.${ext}",
                              "useOriginalTracks": true,
                              "dubTracks": [
                                {
                                  "language": "es",
                                  "title": "Spanish"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    "storage": {
                      "connection": null,
                      "path": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/",
                      "inputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/input/",
                      "outputPath": "s3://sw-vod-express/projects/141/01m3k8jnrdw509457nhz7w81bq/output/",
                      "inputPrefix": null,
                      "outputPrefix": null
                    },
                    "meta": {
                      "createdAt": "2026-09-28T05:42:35.534Z",
                      "updatedAt": "2026-09-28T05:42:35.534Z",
                      "createdBy": {
                        "accountId": 141,
                        "accountName": "Acme Media",
                        "userId": null,
                        "userEmail": null,
                        "userFullName": null
                      },
                      "configLocked": false,
                      "messages": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "409": {
            "description": "SyncWords support manages the project’s configuration for now (`meta.configLocked` is `true`), and the call would change `config` or `buildVersion`: `project_config_locked`. Nothing is stored. `name` and `description` can still be changed, and a `config` equal to the stored one is accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The project's configuration is managed by SyncWords support and cannot be changed right now."
                  ],
                  "code": "project_config_locked"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (`validation_failed`): nothing is stored. Or the change was stored and the restarted service did not start (`service_start_failed`): the body carries the project’s `id`, the project reads `error`, and `POST …/start` retries it with the new config.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "validation_failed",
                  "message": "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio. (and 2 more errors)",
                  "errors": {
                    "/config/outputs/0/items/0/dubTracks/0/language": [
                      "No translation has ttsOptions, so there is no dub to add: use useOriginalTracks for the input’s audio."
                    ],
                    "/config/outputs/0/items/0/nameTemplate": [
                      "The name template must contain ${name}, or every input would write the same file."
                    ],
                    "/config/translations/0/language": [
                      "Language 'xx' is not supported by engine 'DeepL'"
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "The running processing service could not be stopped: `service_stop_failed`. Nothing was changed; repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Failed to stop the running processing service"
                  ],
                  "code": "service_stop_failed"
                }
              }
            }
          }
        },
        "operationId": "updateProject"
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Stops the project’s processing service and deletes the project. **Its files are kept** in storage; download what you need first. The project is deleted only once its service is confirmed stopped.\n\n**Guide:** [Projects](/vod-express/concepts/projects)",
        "responses": {
          "204": {
            "description": "Deleted. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "The running processing service could not be stopped: `service_stop_failed`. Nothing was changed; repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Failed to stop the running processing service"
                  ],
                  "code": "service_stop_failed"
                }
              }
            }
          }
        },
        "operationId": "deleteProject"
      }
    },
    "/projects/{id}/start": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Start a project",
        "description": "Starts the processing service, which then picks up every unprocessed file in `input/`. A healthy service is left as it is, so it is safe to call after every upload. One that failed is replaced: this is also the retry after a failed start.\n\n**Guide:** [The processing service](/vod-express/concepts/projects#the-processing-service)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ServiceStatus"
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "ACTIVE",
                    "orchestratorStatus": "ready",
                    "serviceId": "vod-01m3k8jnrdw509457nhz7w81bq-nhx79dps",
                    "launchedAt": "2026-09-28T05:43:02.000Z",
                    "version": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "projectStatus": "running",
                    "lastError": null,
                    "pod": {
                      "status": "ready",
                      "message": null,
                      "at": "2026-09-28T05:43:04.969Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "422": {
            "description": "The service did not start: `service_start_failed`. Repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "The running processing service could not be stopped: `service_stop_failed`. Nothing was changed; repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Failed to stop the running processing service"
                  ],
                  "code": "service_stop_failed"
                }
              }
            }
          },
          "503": {
            "description": "The processing platform could not be asked about the current service, so no second one was started: `platform_unavailable`. Repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The processing service cannot be reached to check its state; try again."
                  ],
                  "code": "platform_unavailable"
                }
              }
            }
          }
        },
        "operationId": "startProject"
      }
    },
    "/projects/{id}/stop": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Stop a project",
        "description": "Stops the processing service, and answers once it is gone, so a start straight after never runs two. Files are left as they are; those not processed yet wait for the next start.\n\n**Guide:** [The processing service](/vod-express/concepts/projects#the-processing-service)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ServiceStatus"
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "NOT_RUNNING",
                    "orchestratorStatus": null,
                    "serviceId": null,
                    "launchedAt": null,
                    "version": null,
                    "projectStatus": "stopped",
                    "lastError": null,
                    "pod": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "502": {
            "description": "The running processing service could not be stopped: `service_stop_failed`. Nothing was changed; repeat the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Failed to stop the running processing service"
                  ],
                  "code": "service_stop_failed"
                }
              }
            }
          }
        },
        "operationId": "stopProject"
      }
    },
    "/projects/{id}/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get the service status",
        "description": "What the project’s processing service is doing now: up (`ACTIVE`), stopped (`NOT_RUNNING`), or failed (`ERROR`, which `POST …/start` repairs).\n\n**Guide:** [Monitoring](/vod-express/guides/monitoring)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ServiceStatus"
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "ACTIVE",
                    "orchestratorStatus": "ready",
                    "serviceId": "vod-01m3k8jnrdw509457nhz7w81bq-nhx79dps",
                    "launchedAt": "2026-09-28T05:43:02.000Z",
                    "version": "0.9.0-alpha1-2026-10-20T14-10Z-3f05aa",
                    "projectStatus": "running",
                    "lastError": null,
                    "pod": {
                      "status": "ready",
                      "message": null,
                      "at": "2026-09-28T05:43:04.969Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getProjectStatus"
      }
    },
    "/projects/{id}/source-feed": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Check the source feed",
        "description": "A feed project’s `sourceMedia` feed, fetched now and read the way the project reads it (`sourceType` says how: an `RssSource` takes each item’s `<enclosure>`, an `MrssSource` its `<media:content>`): whether it answers, how many items it has, and which items the project skips because they link no media file it can download. Saving a project runs the same check on a new feed URL, and refuses a feed that does not answer, is not RSS, or has items and none the project can download. A result is reused for up to a minute.\n\n**Guide:** [Media RSS feeds](/vod-express/concepts/inputs#media-rss-feeds)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SourceFeed"
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://feeds.example.com/videos.xml",
                    "sourceType": "MrssSource",
                    "checkedAt": "2026-10-06T19:10:32.000Z",
                    "state": "no_usable_items",
                    "problem": "None of the feed’s 25 items has a media file to download (the first: no <media:content>). Each item needs a <media:content url=\"…\"> linking the file itself, such as an .mp4.",
                    "httpStatus": 200,
                    "items": 25,
                    "usableItems": 0,
                    "skippedItems": [
                      {
                        "guid": "https://www.example.com/video/6406369231112",
                        "title": "Fisherman rescued after calling for help",
                        "link": "https://www.example.com/video/6406369231112",
                        "reason": "no <media:content>"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "409": {
            "description": "The project reads its media from its input folder: `project_reads_folder`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "This project reads its media from its input folder, not a feed."
                  ],
                  "code": "project_reads_folder"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getProjectSourceFeed"
      }
    },
    "/source-feeds/check": {
      "post": {
        "tags": [
          "Feeds"
        ],
        "summary": "Check a feed before saving it",
        "description": "Reads a feed the way a feed project reads it, before there is a project: the same checks and the same answer as `GET /projects/{id}/source-feed`. `sourceType` says how to read it: `RssSource` takes each item’s `<enclosure>`, `MrssSource` (the default) its `<media:content>`. Saving a project with this feed as its `sourceMedia` refuses it when `state` is `no_usable_items`, `unreachable` or `not_a_feed`. A result is reused for up to a minute.\n\n**Guide:** [Media RSS feeds](/vod-express/concepts/inputs#media-rss-feeds)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The feed: `https://`, at most 2048 characters, on a host that resolves to a public address."
                  },
                  "sourceType": {
                    "enum": [
                      "RssSource",
                      "MrssSource"
                    ],
                    "default": "MrssSource",
                    "description": "How the feed is read: as an RSS feed, the media in each item’s `<enclosure>` (`RssSource`), or as a Media RSS feed, each item’s `<media:content>` (`MrssSource`)."
                  }
                }
              },
              "example": {
                "url": "https://feeds.example.com/videos.xml",
                "sourceType": "MrssSource"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SourceFeed"
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://feeds.example.com/videos.xml",
                    "sourceType": "MrssSource",
                    "checkedAt": "2026-10-06T19:10:32.000Z",
                    "state": "no_usable_items",
                    "problem": "None of the feed’s 25 items has a media file to download (the first: no <media:content>). Each item needs a <media:content url=\"…\"> linking the file itself, such as an .mp4.",
                    "httpStatus": 200,
                    "items": 25,
                    "usableItems": 0,
                    "skippedItems": [
                      {
                        "guid": "https://www.example.com/video/6406369231112",
                        "title": "Fisherman rescued after calling for help",
                        "link": "https://www.example.com/video/6406369231112",
                        "reason": "no <media:content>"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "No `url`, or one a project could not read: not `https://`, with a user name or password, or on a host that does not resolve to a public address; a `sourceType` other than the two (`validation_failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "checkSourceFeed"
      }
    },
    "/projects/{id}/config": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get the config",
        "description": "The project’s `config` as stored: what you sent, with the engines chosen for you where you left them out. `Accept: application/yaml` answers YAML.\n\n**Guide:** [The project config](/vod-express/configuration/overview)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Config",
                      "description": "The processing instructions, validated against the schema of the project’s build. Shown here: build `0.9.0-alpha1-2026-10-20T14-10Z-3f05aa`; `GET /schema` has any build’s."
                    }
                  }
                },
                "example": {
                  "data": {
                    "sourceMedia": {
                      "@type": "S3FolderSource"
                    },
                    "transcription": {
                      "@type": "Speechmatics",
                      "language": "en"
                    },
                    "translations": [
                      {
                        "language": "es",
                        "engine": {
                          "@type": "Gemini"
                        },
                        "ttsOptions": {
                          "provider": {
                            "@type": "Deepdub",
                            "voice": "auto"
                          }
                        }
                      }
                    ],
                    "outputs": [
                      {
                        "@type": "S3FolderOutput",
                        "items": [
                          {
                            "@type": "VideoFile",
                            "ext": "mp4",
                            "nameTemplate": "${name}.es.${ext}",
                            "useOriginalTracks": true,
                            "dubTracks": [
                              {
                                "language": "es",
                                "title": "Spanish"
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getProjectConfig"
      }
    },
    "/projects/{id}/files": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List input files",
        "description": "Every file in the project’s `input/` folder, subfolders included, in name order: its processing state and the outputs it produces. This is the call to poll: `meta` counts the files in each state, so the project is finished when `meta.pending` and `meta.processing` are both 0.\n\n**Guide:** [Monitoring](/vod-express/guides/monitoring)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Only the files in this state, or in any of several, comma-separated: `failed`, `pending,processing`. `meta` still counts every file.",
            "schema": {
              "type": "string"
            },
            "example": "failed"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InputFile"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/FileCounts"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "name": "episode 01.mp4",
                      "size": 11825290,
                      "uploadedAt": "2026-09-28T06:10:46.000Z",
                      "status": "done",
                      "hash": "a7381118",
                      "outputs": [
                        {
                          "path": "episode 01.es.mp4",
                          "available": true,
                          "size": 12374212
                        }
                      ],
                      "failureReason": null,
                      "failureCode": null,
                      "stale": false,
                      "usage": [
                        {
                          "kind": "captioning",
                          "language": "en",
                          "durationMs": 1312480
                        },
                        {
                          "kind": "translation",
                          "language": "es",
                          "durationMs": 1312480
                        },
                        {
                          "kind": "dubbing",
                          "language": "es",
                          "durationMs": 1312480
                        }
                      ]
                    },
                    {
                      "name": "season 2/episode 01.mp4",
                      "size": 734003200,
                      "uploadedAt": "2026-09-28T06:12:03.000Z",
                      "status": "pending",
                      "hash": null,
                      "outputs": [
                        {
                          "path": "season 2/episode 01.es.mp4",
                          "available": false,
                          "size": null
                        }
                      ],
                      "failureReason": null,
                      "failureCode": null,
                      "stale": false,
                      "usage": null
                    },
                    {
                      "name": "trailer.mp4",
                      "size": 3174578,
                      "uploadedAt": "2026-09-28T06:10:44.000Z",
                      "status": "failed",
                      "hash": null,
                      "outputs": [
                        {
                          "path": "trailer.es.mp4",
                          "available": false,
                          "size": null
                        }
                      ],
                      "failureReason": "Processing this file failed. Retry it, or contact support if it fails again.",
                      "failureCode": "no_audio",
                      "stale": false,
                      "usage": null
                    }
                  ],
                  "meta": {
                    "total": 3,
                    "pending": 1,
                    "processing": 0,
                    "done": 1,
                    "failed": 1
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "422": {
            "description": "A `status` that is not a file state: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "The selected status is invalid.",
                  "errors": {
                    "status": [
                      "The selected status is invalid."
                    ]
                  },
                  "code": "validation_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listFiles"
      }
    },
    "/projects/{id}/files/{name}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The input file’s name, as `GET /projects/{id}/files` lists it, URL-encoded as **one** path segment: its slashes are encoded too. `episode 01.mp4` is `episode%2001.mp4`; `season 2/episode 01.mp4` is `season%202%2Fepisode%2001.mp4` (`encodeURIComponent()`, `jq @uri`, `urllib.parse.quote(name, safe=\"\")`).",
          "example": "season 2/episode 01.mp4"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get an input file",
        "description": "One file of `GET …/files`: its processing state and the outputs it produces. The cheaper call to poll when you are waiting for one file.\n\n**Guide:** [Monitoring](/vod-express/guides/monitoring)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InputFile"
                    }
                  }
                },
                "example": {
                  "data": {
                    "name": "episode 01.mp4",
                    "size": 11825290,
                    "uploadedAt": "2026-09-28T06:10:46.000Z",
                    "status": "done",
                    "hash": "a7381118",
                    "outputs": [
                      {
                        "path": "episode 01.es.mp4",
                        "available": true,
                        "size": 12374212
                      }
                    ],
                    "failureReason": null,
                    "failureCode": null,
                    "stale": false,
                    "usage": [
                      {
                        "kind": "captioning",
                        "language": "en",
                        "durationMs": 1312480
                      },
                      {
                        "kind": "translation",
                        "language": "es",
                        "durationMs": 1312480
                      },
                      {
                        "kind": "dubbing",
                        "language": "es",
                        "durationMs": 1312480
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project (`project_not_found`), or no input of that name (`file_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "File not found.",
                  "code": "file_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getFile"
      },
      "delete": {
        "tags": [
          "Files"
        ],
        "summary": "Delete an input",
        "description": "Deletes an input file and its processing records. Its outputs are kept. A file the running project may be processing — neither `done` nor `failed` yet — is not deleted: stop the project first, or wait.\n\n**Guide:** [Files](/vod-express/concepts/files)",
        "responses": {
          "204": {
            "description": "Deleted. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project (`project_not_found`), or no input of that name (`file_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "File not found.",
                  "code": "file_not_found"
                }
              }
            }
          },
          "409": {
            "description": "The running project may be processing the file: `file_processing`. Wait until it is `done` or `failed`, or stop the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The file may be being processed. Wait until it is done or failed, or stop the project, then try again."
                  ],
                  "code": "file_processing"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "deleteFile"
      }
    },
    "/projects/{id}/files/{name}/download": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The input file’s name, as `GET /projects/{id}/files` lists it, URL-encoded as **one** path segment: its slashes are encoded too. `episode 01.mp4` is `episode%2001.mp4`; `season 2/episode 01.mp4` is `season%202%2Fepisode%2001.mp4` (`encodeURIComponent()`, `jq @uri`, `urllib.parse.quote(name, safe=\"\")`).",
          "example": "season 2/episode 01.mp4"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download an input",
        "description": "A presigned S3 URL of the input file, valid for up to an hour: `expiresAt` says until when. Fetch it without the `Authorization` header; it downloads the file under its own name.\n\n**Guide:** [Files: download](/vod-express/concepts/files#download)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DownloadLink"
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://sw-vod-express.s3.us-east-1.amazonaws.com/projects/141/01m3k8jnrdw509457nhz7w81bq/input/episode%2001.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=…",
                    "expiresAt": "2026-09-28T07:12:03.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project (`project_not_found`), or no input of that name (`file_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "File not found.",
                  "code": "file_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "downloadFile"
      }
    },
    "/projects/{id}/files/{name}/meta": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The input file’s name, as `GET /projects/{id}/files` lists it, URL-encoded as **one** path segment: its slashes are encoded too. `episode 01.mp4` is `episode%2001.mp4`; `season 2/episode 01.mp4` is `season%202%2Fepisode%2001.mp4` (`encodeURIComponent()`, `jq @uri`, `urllib.parse.quote(name, safe=\"\")`).",
          "example": "season 2/episode 01.mp4"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get file metadata",
        "description": "What processing recorded about the file’s latest run, filled in as it goes: `hash`, the run’s id (quote it to support), and `startedAt` when it starts, `durationMs` once the media is read, `dubbingMs` after transcription, translation and dubbing, `finishedAt` when the outputs are written. A run before 2026-10-09 may carry other fields.\n\n**Guide:** [Files](/vod-express/concepts/files)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "inputVersion": {
                          "type": "string",
                          "description": "The version of the file the run read (its ETag)."
                        },
                        "startedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "finishedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "durationMs": {
                          "type": "integer",
                          "description": "The media’s duration."
                        },
                        "dubbingMs": {
                          "type": "integer",
                          "description": "How long transcription, translation and dubbing took."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "hash": "a7381118",
                    "inputVersion": "\"9b2cf535f27731c974343645a3985328\"",
                    "startedAt": "2026-09-28T06:11:02.114Z",
                    "finishedAt": "2026-09-28T06:40:51.872Z",
                    "durationMs": 1312480,
                    "dubbingMs": 1388617
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project or file, or nothing recorded yet: `file_meta_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Nothing has been recorded about this file yet.",
                  "code": "file_meta_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getFileMeta"
      }
    },
    "/projects/{id}/files/{name}/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The input file’s name, as `GET /projects/{id}/files` lists it, URL-encoded as **one** path segment: its slashes are encoded too. `episode 01.mp4` is `episode%2001.mp4`; `season 2/episode 01.mp4` is `season%202%2Fepisode%2001.mp4` (`encodeURIComponent()`, `jq @uri`, `urllib.parse.quote(name, safe=\"\")`).",
          "example": "season 2/episode 01.mp4"
        }
      ],
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Retry a file",
        "description": "Processes the file again, from the start and with the project’s current config: after a failure, or after changing the config. A running project picks it up at its next look at the folder. A project that ran before and is `stopped` now is started again; one that never ran or is in `error` is left as it is, and `projectStatus` says which. A file is not billed again unless its content changed.\n\n**Guide:** [Monitoring](/vod-express/guides/monitoring#when-a-file-failed)",
        "responses": {
          "202": {
            "description": "Queued",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "name",
                        "status",
                        "projectStatus"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "status": {
                          "const": "pending"
                        },
                        "projectStatus": {
                          "enum": [
                            "running",
                            "stopped",
                            "error"
                          ],
                          "description": "The project’s state after the call. `running`: the file will be processed. `stopped`: the project never ran; start it when every file is uploaded. `error`: its last start failed; see `GET …/status`, then `POST …/start`."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "name": "season 2/episode 01.mp4",
                    "status": "pending",
                    "projectStatus": "running"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project (`project_not_found`), or no input of that name (`file_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "File not found.",
                  "code": "file_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "retryFile"
      }
    },
    "/projects/{id}/outputs": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List outputs",
        "description": "Every file in the project’s `output/` folder, subfolders included.\n\n**Guide:** [Files: download](/vod-express/concepts/files#download)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OutputFile"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "path": "episode 01.es.mp4",
                      "size": 12374212,
                      "lastModified": "2026-09-28T06:18:40.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listOutputs"
      }
    },
    "/projects/{id}/outputs/download": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download an output",
        "description": "A presigned S3 URL of an output file, valid for up to an hour: `expiresAt` says until when. Fetch it without the `Authorization` header; it downloads the file under its own name.\n\n**Guide:** [Files: download](/vod-express/concepts/files#download)",
        "parameters": [
          {
            "name": "path",
            "in": "query",
            "required": true,
            "description": "The output’s path under `output/`, as `GET …/outputs` or an input’s `outputs` lists it, URL-encoded: `season%202%2Fepisode%2001.es.mp4`.",
            "schema": {
              "type": "string"
            },
            "example": "season 2/episode 01.es.mp4"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DownloadLink"
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://sw-vod-express.s3.us-east-1.amazonaws.com/projects/141/01m3k8jnrdw509457nhz7w81bq/output/episode%2001.es.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=…",
                    "expiresAt": "2026-09-28T07:18:40.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "A path outside `output/`: `invalid_output_path`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Invalid output path."
                  ],
                  "code": "invalid_output_path"
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project, or no such output: `output_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "downloadOutput"
      }
    },
    "/feeds/{projectId}/{language}/feed.xml": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "description": "The project’s id.",
          "schema": {
            "type": "string"
          },
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "language",
          "in": "path",
          "required": true,
          "description": "The feed’s language: the `language` of an `RssOutput` or `MrssOutput` in the project’s config.",
          "schema": {
            "type": "string"
          },
          "example": "es"
        }
      ],
      "get": {
        "tags": [
          "Feeds"
        ],
        "summary": "Get an output feed",
        "description": "A feed project’s re-published feed in one language (an `RssOutput` or `MrssOutput` of its config): the source feed with each item’s media replaced by the dubbed file and `<channel><language>` set. Items whose file is not ready yet are left out. Public, without a token: give this URL to players and aggregators. Re-written as files finish and every 30 seconds.\n\n**Guide:** [Media RSS feeds](/vod-express/concepts/inputs#media-rss-feeds)",
        "security": [],
        "responses": {
          "200": {
            "description": "The feed.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such project or feed, or the feed has not been written yet: `feed_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Feed not found.",
                  "code": "feed_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getFeed"
      }
    },
    "/feeds/{projectId}/{language}/media/{name}": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "description": "The project’s id.",
          "schema": {
            "type": "string"
          },
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "language",
          "in": "path",
          "required": true,
          "description": "The feed’s language: the `language` of an `RssOutput` or `MrssOutput` in the project’s config.",
          "schema": {
            "type": "string"
          },
          "example": "es"
        }
      ],
      "get": {
        "tags": [
          "Feeds"
        ],
        "summary": "Get a feed’s media file",
        "description": "Where an output feed links each item’s media: a redirect to a presigned URL of the file, valid for six hours. Public, without a token.\n\n**Guide:** [Media RSS feeds](/vod-express/concepts/inputs#media-rss-feeds)",
        "security": [],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The file’s name, as the feed links it.",
            "schema": {
              "type": "string"
            },
            "example": "4f1c2b9a7d3e.8a2b4c6d.mp4"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirects to the file (`Location`).",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "404": {
            "description": "No such project, feed or file: `feed_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Feed not found.",
                  "code": "feed_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getFeedMedia"
      }
    },
    "/projects/{id}/uploads": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        }
      ],
      "post": {
        "tags": [
          "Uploads"
        ],
        "summary": "Start an upload",
        "description": "Starts an upload of one file into the project’s `input/` folder; your client sends the bytes straight to S3, two ways.\n\n**One `PUT`**, for a file of 5 GB or under that is not replacing one: `PUT` the whole file to `url`, with no other header needed, and you are done. The file appears in `input/` when the `PUT` completes. `url` is absent for a larger file, and when a file of that name is already in `input/` (delete it first, or send parts). A project that stopped itself after its last file starts again within a minute (unless its `startOnNewFiles` is false); any other stopped project is not started by it: `POST …/start` afterwards.\n\n**Parts**, for any size, resumable: cut the file into `partCount` parts of `partSize` bytes (the last one shorter), sign each with `GET …/uploads/{uploadId}/parts/{partNumber}`, `PUT` its bytes to the URL, keep the `ETag` header S3 answers, and send them all to `POST …/uploads/{uploadId}/complete`. The file appears in `input/` only once complete.\n\nEither way a running project never picks up half a file. An upload left unfinished is discarded after three days.\n\n**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "size"
                ],
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The file’s name under `input/`; `season 2/episode 01.mp4` puts it in a subfolder. It needs a media extension, and must not contain `${`."
                  },
                  "size": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5497558138880,
                    "description": "Its size in bytes; it decides `partSize` and `partCount`."
                  },
                  "type": {
                    "type": "string",
                    "description": "Its media type, stored with the file by a parts upload. A one-`PUT` upload stores the `Content-Type` header the `PUT` sends, if any."
                  },
                  "overwrite": {
                    "type": "boolean",
                    "default": false,
                    "description": "Replace a file of the same name. The new file is processed, and billed, again. Replace a `failed` file under a new name instead: its old content would be processed again."
                  }
                }
              },
              "example": {
                "name": "season 2/episode 01.mp4",
                "size": 734003200,
                "type": "video/mp4"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Started",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Upload"
                    }
                  }
                },
                "example": {
                  "data": {
                    "uploadId": "kuoFY0gBzJF3QDKXnQxnv0epFyjyDyUuF20w4yjsjCE4Sq9hkf7Fn.QWqeFXYSSx03HDx…",
                    "key": "season 2/episode 01.mp4",
                    "partSize": 16777216,
                    "partCount": 44,
                    "url": "https://sw-vod-express.s3.us-east-1.amazonaws.com/projects/141/01m3k8jnrdw509457nhz7w81bq/input/season%202/episode%2001.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=…",
                    "urlExpiresAt": "2026-09-28T07:10:46.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project in your account: `project_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Project not found.",
                  "code": "project_not_found"
                }
              }
            }
          },
          "409": {
            "description": "A file of that name is already in `input/`, and `overwrite` is not set: `file_exists`. Or the project reads its media from a feed, and takes no uploads: `project_reads_feed`. Or `overwrite` is set but the running project may be processing that file, which is neither `done` nor `failed` yet: `file_processing`; wait, or stop the project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "A file with this name is already in the project. Send overwrite: true to replace it."
                  ],
                  "code": "file_exists"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "An invalid name, size or type (`validation_failed`). Also a name whose outputs another input already writes: outputs are named after the input without its extension, so `a.mov` and `a.mkv` would both write `a.es.mp4`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "startUpload"
      }
    },
    "/projects/{id}/uploads/{uploadId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The `uploadId` `POST /projects/{id}/uploads` answered, URL-encoded."
        }
      ],
      "delete": {
        "tags": [
          "Uploads"
        ],
        "summary": "Abort an upload",
        "description": "Abandons an upload, discarding the parts sent.\n\n**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `key` `POST /projects/{id}/uploads` answered: the file’s name under `input/`, URL-encoded.",
            "example": "season 2/episode 01.mp4"
          }
        ],
        "responses": {
          "204": {
            "description": "Aborted, or already gone. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project, or no such upload for that key — completed, aborted, expired or never started: `upload_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Upload not found.",
                  "code": "upload_not_found"
                }
              }
            }
          },
          "422": {
            "description": "A key that is not a name under `input/`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "abortUpload"
      }
    },
    "/projects/{id}/uploads/{uploadId}/parts": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The `uploadId` `POST /projects/{id}/uploads` answered, URL-encoded."
        }
      ],
      "get": {
        "tags": [
          "Uploads"
        ],
        "summary": "List uploaded parts",
        "description": "The parts S3 already holds for an upload, to resume one after an interruption: send only the others, then complete with every part.\n\n**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `key` `POST /projects/{id}/uploads` answered: the file’s name under `input/`, URL-encoded.",
            "example": "season 2/episode 01.mp4"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "partNumber": {
                            "type": "integer"
                          },
                          "size": {
                            "type": "integer"
                          },
                          "etag": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "partNumber": 1,
                      "size": 16777216,
                      "etag": "08b46181d7094b5ece88bb389c7499af"
                    },
                    {
                      "partNumber": 2,
                      "size": 16777216,
                      "etag": "5d41402abc4b2a76b9719d911017c592"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project, or no such upload for that key — completed, aborted, expired or never started: `upload_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Upload not found.",
                  "code": "upload_not_found"
                }
              }
            }
          },
          "422": {
            "description": "A key that is not a name under `input/`: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listUploadParts"
      }
    },
    "/projects/{id}/uploads/{uploadId}/parts/{partNumber}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The `uploadId` `POST /projects/{id}/uploads` answered, URL-encoded."
        },
        {
          "name": "partNumber",
          "in": "path",
          "required": true,
          "description": "The part, from 1 to `partCount`.",
          "schema": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000
          },
          "example": 1
        }
      ],
      "get": {
        "tags": [
          "Uploads"
        ],
        "summary": "Sign a part",
        "description": "A presigned URL to `PUT` one part’s bytes to, with no other headers. S3’s answer carries the part’s `ETag` header: keep it for completing. Sign each part just before sending it: a URL is valid until `expiresAt`, an hour at most and sometimes less. Counted against a budget of its own, 10,000 a minute per account, not the API’s: a large file is thousands of parts.\n\n**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `key` `POST /projects/{id}/uploads` answered: the file’s name under `input/`, URL-encoded.",
            "example": "season 2/episode 01.mp4"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DownloadLink"
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://sw-vod-express.s3.us-east-1.amazonaws.com/projects/141/01m3k8jnrdw509457nhz7w81bq/input/season%202/episode%2001.mp4?partNumber=1&uploadId=kuoFY0gBzJF3QDKXnQxnv0epFyjyDyUuF20w4yjsjCE4Sq9hkf7Fn.QWqeFXYSSx03HDx…&X-Amz-Signature=…",
                    "expiresAt": "2026-09-28T06:43:02.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project, or no such upload for that key — completed, aborted, expired or never started: `upload_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Upload not found.",
                  "code": "upload_not_found"
                }
              }
            }
          },
          "422": {
            "description": "A key or part number that is not valid: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 10,000 parts signed in a minute by your account: `too_many_requests`. Wait the seconds `Retry-After` says.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "signUploadPart"
      }
    },
    "/projects/{id}/uploads/{uploadId}/complete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-z]{26}$"
          },
          "description": "The project id, a lowercase ULID.",
          "example": "01m3k8jnrdw509457nhz7w81bq"
        },
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The `uploadId` `POST /projects/{id}/uploads` answered, URL-encoded."
        }
      ],
      "post": {
        "tags": [
          "Uploads"
        ],
        "summary": "Complete an upload",
        "description": "Assembles the parts: the file is now in `input/`, and a running project picks it up. A project that ran before and is `stopped` now (auto-stopped after its last file, or stopped by hand) is started again, unless its `startOnNewFiles` is false; one that never ran (`autoStart: false`) or is in `error` is left as it is, and `projectStatus` says which. Replacing a file (`overwrite`) also drops what was recorded about the old one, so the new one is processed from scratch. Safe to repeat with the same body.\n\n**Guide:** [Upload through the API](/vod-express/uploads/api-uploads)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key",
                  "parts"
                ],
                "additionalProperties": false,
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "The `key` the upload started with."
                  },
                  "parts": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10000,
                    "description": "Every part, the ones listed by `GET …/parts` included.",
                    "items": {
                      "type": "object",
                      "required": [
                        "partNumber",
                        "etag"
                      ],
                      "properties": {
                        "partNumber": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 10000
                        },
                        "etag": {
                          "type": "string",
                          "description": "The `ETag` header S3 answered the part’s `PUT` with, quoted or not."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "key": "season 2/episode 01.mp4",
                "parts": [
                  {
                    "partNumber": 1,
                    "etag": "\"08b46181d7094b5ece88bb389c7499af\""
                  },
                  {
                    "partNumber": 2,
                    "etag": "\"5d41402abc4b2a76b9719d911017c592\""
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Complete",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "key",
                        "projectStatus"
                      ],
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "projectStatus": {
                          "enum": [
                            "running",
                            "stopped",
                            "error"
                          ],
                          "description": "The project’s state after the call. `running`: the file will be processed. `stopped`: the project never ran; start it when every file is uploaded. `error`: its last start failed; see `GET …/status`, then `POST …/start`."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "key": "season 2/episode 01.mp4",
                    "projectStatus": "running"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such project, or no such upload for that key — completed, aborted, expired or never started: `upload_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Upload not found.",
                  "code": "upload_not_found"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "An invalid body, or parts S3 refuses — a wrong ETag, a part it does not hold, a part under 5 MiB other than the last: `validation_failed`, under `errors.parts`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "completeUpload"
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "description": "Your account’s webhooks, oldest first. Secrets are not shown.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "wh_01m3n2t4k8b5x0c7d2e9f6g1h3",
                      "url": "https://hooks.example.com/syncwords",
                      "events": [
                        "file.done",
                        "file.failed"
                      ],
                      "createdAt": "2026-09-28T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listWebhooks"
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook",
        "description": "Subscribes an HTTPS URL to events: each is POSTed to it as JSON, signed with the `secret` this answer carries. **The secret is shown only here**: keep it to verify deliveries. The URL must be `https://` and reach a public address. At most 10 webhooks per account, one per URL.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "examples": [
                      "https://hooks.example.com/syncwords"
                    ]
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "file.done",
                        "file.failed",
                        "project.stopped",
                        "project.error"
                      ]
                    }
                  }
                }
              },
              "example": {
                "url": "https://hooks.example.com/syncwords",
                "events": [
                  "file.done",
                  "file.failed"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Webhook"
                        },
                        {
                          "type": "object",
                          "required": [
                            "secret"
                          ],
                          "properties": {
                            "secret": {
                              "type": "string",
                              "description": "The signing secret, `whsec_…`. Shown only in this answer."
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "wh_01m3n2t4k8b5x0c7d2e9f6g1h3",
                    "url": "https://hooks.example.com/syncwords",
                    "events": [
                      "file.done",
                      "file.failed"
                    ],
                    "createdAt": "2026-09-28T09:00:00.000Z",
                    "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "An invalid body, a URL that is not `https://` or not public, or one the account already has (`validation_failed`); or the account has 10 webhooks already (`webhook_limit_reached`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "The URL’s host does not resolve to a public address, so it cannot receive webhooks.",
                  "errors": {
                    "url": [
                      "The URL’s host does not resolve to a public address, so it cannot receive webhooks."
                    ]
                  },
                  "code": "validation_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "Webhooks cannot be created right now: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWebhook"
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The webhook’s `id`: `wh_…`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Get a webhook",
        "description": "One webhook. The secret is not shown.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "wh_01m3n2t4k8b5x0c7d2e9f6g1h3",
                    "url": "https://hooks.example.com/syncwords",
                    "events": [
                      "file.done",
                      "file.failed"
                    ],
                    "createdAt": "2026-09-28T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "getWebhook"
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Change a webhook",
        "description": "Changes the URL, the events, or both; what is not sent is kept. The id and the secret stay, and deliveries not yet made go to the new URL. The URL is checked as at create.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "file.done",
                        "file.failed",
                        "project.stopped",
                        "project.error"
                      ]
                    }
                  }
                }
              },
              "example": {
                "events": [
                  "file.done",
                  "file.failed",
                  "project.error"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "wh_01m3n2t4k8b5x0c7d2e9f6g1h3",
                    "url": "https://hooks.example.com/syncwords",
                    "events": [
                      "file.done",
                      "file.failed",
                      "project.error"
                    ],
                    "createdAt": "2026-09-28T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 2 MB: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "The request body is too large."
                  ],
                  "code": "payload_too_large"
                }
              }
            }
          },
          "415": {
            "description": "The body is neither JSON nor YAML: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Send the body as JSON (Content-Type: application/json) or YAML (Content-Type: application/yaml)."
                  ],
                  "code": "unsupported_media_type"
                }
              }
            }
          },
          "422": {
            "description": "An invalid body, a URL that is not `https://` or not public, or one the account already has: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "This account already has a webhook for this URL.",
                  "errors": {
                    "url": [
                      "This account already has a webhook for this URL."
                    ]
                  },
                  "code": "validation_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "updateWebhook"
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook",
        "description": "Stops sending to it at once; deliveries not yet made are dropped.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "204": {
            "description": "Deleted",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "deleteWebhook"
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The webhook’s `id`: `wh_…`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate the secret",
        "description": "Replaces the signing secret, answering the new one — **shown only here**, as at create. For the next 24 hours (`previousSecretExpiresAt`) every delivery is signed with both secrets, the new one first, so your server can switch to the new one without missing a delivery.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Webhook"
                        },
                        {
                          "type": "object",
                          "required": [
                            "secret",
                            "previousSecretExpiresAt"
                          ],
                          "properties": {
                            "secret": {
                              "type": "string",
                              "description": "The new signing secret, `whsec_…`. Shown only in this answer."
                            },
                            "previousSecretExpiresAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "Until when the secret it replaces signs deliveries too."
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "wh_01m3n2t4k8b5x0c7d2e9f6g1h3",
                    "url": "https://hooks.example.com/syncwords",
                    "events": [
                      "file.done",
                      "file.failed"
                    ],
                    "createdAt": "2026-09-28T09:00:00.000Z",
                    "secret": "whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oD",
                    "previousSecretExpiresAt": "2026-09-29T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "Webhooks cannot be changed right now: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "rotateWebhookSecret"
      }
    },
    "/webhooks/{id}/deliveries": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The webhook’s `id`: `wh_…`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List deliveries",
        "description": "The webhook’s deliveries of the past 7 days, newest first, a page at a time: each event, how many attempts it took, and what your server answered last. Follow `links.next` until it is null.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Only the deliveries in this state: `pending` (still being tried), `delivered`, or `failed` (given up).",
            "schema": {
              "enum": [
                "pending",
                "delivered",
                "failed"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "The page, from 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "perPage",
            "in": "query",
            "description": "Deliveries a page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryPage"
                },
                "example": {
                  "data": [
                    {
                      "eventId": "evt_01m3n2x5f1h2d9k7c3b8a6w4q0",
                      "type": "file.done",
                      "status": "delivered",
                      "attempts": 2,
                      "lastStatus": 200,
                      "lastError": null,
                      "lastAttemptAt": "2026-09-28T10:00:35.120Z",
                      "nextAttemptAt": null,
                      "deliveredAt": "2026-09-28T10:00:35.120Z",
                      "createdAt": "2026-09-28T10:00:04.310Z"
                    }
                  ],
                  "links": {
                    "first": "https://api-vod-express.syncwords.com/v1/webhooks/wh_01m3n2t4k8b5x0c7d2e9f6g1h3/deliveries?page=1",
                    "last": "https://api-vod-express.syncwords.com/v1/webhooks/wh_01m3n2t4k8b5x0c7d2e9f6g1h3/deliveries?page=1",
                    "prev": null,
                    "next": null
                  },
                  "meta": {
                    "current_page": 1,
                    "from": 1,
                    "last_page": 1,
                    "per_page": 25,
                    "to": 1,
                    "total": 1
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "422": {
            "description": "A bad filter: `validation_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "The selected status is invalid.",
                  "errors": {
                    "status": [
                      "The selected status is invalid."
                    ]
                  },
                  "code": "validation_failed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "listWebhookDeliveries"
      }
    },
    "/webhooks/{id}/deliveries/{eventId}/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The webhook’s `id`: `wh_…`.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "eventId",
          "in": "path",
          "required": true,
          "description": "The delivery’s `eventId`: `evt_…`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a delivery again",
        "description": "Queues the delivery to be sent again now, with its attempts reset and a fresh 24 hours of retries, whatever its state: a `failed` one after your server is fixed, or a `delivered` one your server lost. It goes with the same `webhook-id`, so a server that already handled it can drop it.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "202": {
            "description": "Queued",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    }
                  }
                },
                "example": {
                  "data": {
                    "eventId": "evt_01m3n2x5f1h2d9k7c3b8a6w4q0",
                    "type": "file.done",
                    "status": "pending",
                    "attempts": 0,
                    "lastStatus": 200,
                    "lastError": null,
                    "lastAttemptAt": "2026-09-28T10:00:35.120Z",
                    "nextAttemptAt": "2026-09-28T12:00:00.000Z",
                    "deliveredAt": null,
                    "createdAt": "2026-09-28T10:00:04.310Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account (`webhook_not_found`), or it has no delivery of that event (`webhook_delivery_not_found`): deliveries are kept 7 days.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Delivery not found.",
                  "code": "webhook_delivery_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          }
        },
        "operationId": "retryWebhookDelivery"
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The webhook’s `id`: `wh_…`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Sends one `webhook.test` event to the webhook now, signed like every delivery, and answers what your server said. It is not retried, and not listed in the deliveries.\n\n**Guide:** [Webhooks](/vod-express/guides/webhooks)",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookTestResult"
                    }
                  }
                },
                "example": {
                  "data": {
                    "eventId": "evt_01m3n2x5f1h2d9k7c3b8a6w4q0",
                    "delivered": true,
                    "status": 200,
                    "error": null,
                    "durationMs": 184
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or an expired or unknown one: `unauthenticated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Unauthenticated.",
                  "code": "unauthenticated"
                }
              }
            }
          },
          "403": {
            "description": "VOD Express is not available to the account: switched off for it (`vod_express_not_enabled`), or the account is restricted — its access revoked, its trial expired or used up (`account_restricted`, with the reason in `message`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "VOD Express is not enabled for your account."
                  ],
                  "code": "vod_express_not_enabled"
                }
              }
            }
          },
          "404": {
            "description": "No such webhook in your account: `webhook_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "Webhook not found.",
                  "code": "webhook_not_found"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: `too_many_requests`. Wait the seconds `Retry-After` says, then repeat the call.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": [
                    "Too Many Attempts."
                  ],
                  "code": "too_many_requests"
                }
              }
            }
          },
          "503": {
            "description": "Webhooks cannot be sent right now: `service_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "testWebhook"
      }
    }
  },
  "webhooks": {
    "file.done": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A file is done",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileDoneEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds delivers it. Anything else, or no answer, is tried again: after 30 s, 2, 10 and 30 minutes, then hourly, for 24 hours."
          }
        }
      }
    },
    "file.failed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A file failed",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileFailedEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds delivers it. Anything else, or no answer, is tried again: after 30 s, 2, 10 and 30 minutes, then hourly, for 24 hours."
          }
        }
      }
    },
    "project.stopped": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A project stopped",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectStoppedEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds delivers it. Anything else, or no answer, is tried again: after 30 s, 2, 10 and 30 minutes, then hourly, for 24 hours."
          }
        }
      }
    },
    "project.error": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A project’s service failed",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectErrorEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds delivers it. Anything else, or no answer, is tried again: after 30 s, 2, 10 and 30 minutes, then hourly, for 24 hours."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A token from `POST /token/create`, made with your API key pair."
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests your account is allowed a minute.",
        "schema": {
          "type": "integer",
          "examples": [
            1000
          ]
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests your account has left in the current minute.",
        "schema": {
          "type": "integer",
          "examples": [
            998
          ]
        }
      },
      "Retry-After": {
        "description": "Seconds until requests are accepted again.",
        "schema": {
          "type": "integer",
          "examples": [
            42
          ]
        }
      }
    },
    "schemas": {
      "Message": {
        "type": "object",
        "required": [
          "severity",
          "code",
          "path",
          "message"
        ],
        "description": "Something worth changing about a project that was accepted: nothing is refused for it. Worked out from the stored config on every answer, so it shows on a read too, and goes away once the config is fixed.",
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "error"
            ],
            "description": "How much it matters. `warning`: it costs money or loses output. Treat a value you do not know as `info`."
          },
          "code": {
            "type": "string",
            "description": "For programs: stable, one per situation. `unused_translation`: no output carries the translation, which is still produced and billed (and its dub, `params.dubbed`). `unused_dub`: the translation’s subtitles are in an output, its dub in none, and the dub is still produced and billed. `dictionary_not_applied`: a dictionary, postprocessing dictionary or glossary the config names is no longer in the account or in the project’s languages, so the project processes without it (`params.field`, `params.reference`, `params.reason`: `not_found` or `language`). More codes may be added.",
            "examples": [
              "unused_dub",
              "unused_translation",
              "dictionary_not_applied"
            ]
          },
          "path": {
            "type": [
              "string",
              "null"
            ],
            "description": "What it is about: a JSON pointer, the same as the keys of a 422’s `errors`. Null when it is about the whole project.",
            "examples": [
              "/config/translations/0/ttsOptions"
            ]
          },
          "message": {
            "type": "string",
            "description": "For people: what is wrong, what it costs, and what to change."
          },
          "params": {
            "type": "object",
            "description": "The values the message names, for a client that words it itself: `language`, and `dubbed` on `unused_translation`; `field`, `reference` and `reason` on `dictionary_not_applied`.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "message",
          "code"
        ],
        "properties": {
          "message": {
            "description": "For people. A string, or an array of one string on some statuses: read it as `Array.isArray(m) ? m[0] : m`.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "code": {
            "description": "For programs: stable, one per situation. See the Errors guide.",
            "enum": [
              "bad_request",
              "invalid_output_path",
              "invalid_idempotency_key",
              "unauthenticated",
              "invalid_credentials",
              "forbidden",
              "vod_express_not_enabled",
              "account_restricted",
              "not_found",
              "project_not_found",
              "file_not_found",
              "file_meta_not_found",
              "output_not_found",
              "feed_not_found",
              "upload_not_found",
              "webhook_not_found",
              "webhook_delivery_not_found",
              "aws_access_not_provisioned",
              "storage_connection_not_found",
              "method_not_allowed",
              "conflict",
              "file_exists",
              "project_reads_feed",
              "project_reads_folder",
              "file_processing",
              "project_config_locked",
              "idempotency_in_progress",
              "storage_connection_in_use",
              "payload_too_large",
              "unsupported_media_type",
              "validation_failed",
              "service_start_failed",
              "idempotency_key_reused",
              "webhook_limit_reached",
              "storage_failed",
              "storage_connection_check_failed",
              "storage_connection_folder_only",
              "storage_prefix_taken",
              "build_without_storage_connections",
              "build_has_no_schema",
              "too_many_requests",
              "internal_error",
              "bad_gateway",
              "service_stop_failed",
              "aws_access_failed",
              "platform_unavailable",
              "service_unavailable",
              "build_list_unavailable"
            ]
          },
          "errors": {
            "type": "object",
            "description": "On a 422: each field’s messages, keyed by field name, or by JSON pointer inside `config`.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "username",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string",
            "maxLength": 255,
            "description": "Your public key."
          },
          "password": {
            "type": "string",
            "maxLength": 255,
            "description": "Your secret key."
          }
        }
      },
      "Token": {
        "type": "object",
        "required": [
          "access_token",
          "token_type"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Send it as `Authorization: Bearer <access_token>`."
          },
          "token_type": {
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until the token expires. Absent where tokens do not expire by age."
          }
        }
      },
      "Whoami": {
        "type": "object",
        "required": [
          "organization",
          "user",
          "vodExpress"
        ],
        "properties": {
          "organization": {
            "type": "object",
            "description": "The account the token belongs to.",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "user": {
            "type": "object",
            "description": "Null fields for an organization token, which has no user.",
            "required": [
              "id",
              "email",
              "apiUser"
            ],
            "properties": {
              "id": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "apiUser": {
                "type": "boolean",
                "description": "An organization token (an API key pair)."
              }
            }
          },
          "vodExpress": {
            "type": "object",
            "required": [
              "enabled",
              "defaultBuildVersion",
              "hasAwsAccess",
              "canManageAccount"
            ],
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "While false, every other call answers 403."
              },
              "defaultBuildVersion": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The build a new project gets unless it names one."
              },
              "hasAwsAccess": {
                "type": "boolean",
                "description": "An upload role exists for the account (`PUT /account/aws-access`)."
              },
              "canManageAccount": {
                "type": "boolean",
                "description": "Whether this token may change the account: its upload role, storage connections and settings. True for an organization key and for an administrator’s user token."
              }
            }
          }
        }
      },
      "AwsAccess": {
        "type": "object",
        "required": [
          "iamPrincipalArn",
          "roleArn",
          "externalId",
          "bucket",
          "region",
          "s3Prefix"
        ],
        "properties": {
          "iamPrincipalArn": {
            "type": "string",
            "description": "The principal the role trusts."
          },
          "roleArn": {
            "type": "string",
            "description": "Assume this role …"
          },
          "externalId": {
            "type": "string",
            "description": "… with this external id."
          },
          "bucket": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "s3Prefix": {
            "type": "string",
            "description": "Everything the role reaches: your account’s folder, one subfolder a project."
          }
        }
      },
      "StorageConnection": {
        "type": "object",
        "required": [
          "id",
          "name",
          "bucket",
          "endpoint",
          "region",
          "accessKeyId",
          "s3Uri",
          "isDefault",
          "projectsUsing",
          "lastCheck",
          "createdBy",
          "createdAt",
          "updatedAt"
        ],
        "description": "One of your own buckets, with the keys the service reads and writes it with. The secret access key is never answered.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Your name for it, unique in your account."
          },
          "bucket": {
            "type": "string"
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ],
            "description": "The S3-compatible endpoint, `https://…` with no path (Cloudflare R2: `https://<account id>.r2.cloudflarestorage.com`). Null: AWS S3."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "The signing region. Null: `auto` with an endpoint, `us-east-1` without."
          },
          "accessKeyId": {
            "type": "string"
          },
          "s3Uri": {
            "type": "string",
            "description": "`s3://<bucket>/`."
          },
          "isDefault": {
            "type": "boolean",
            "description": "A project created without `storage` uses it (`PATCH /account/settings`)."
          },
          "projectsUsing": {
            "type": "integer",
            "description": "How many of your projects use it; it cannot be deleted while any does."
          },
          "lastCheck": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "ok",
              "checkedAt",
              "error"
            ],
            "description": "The last time the bucket was listed with the keys — on create, on a change to them, and on `POST …/check` — and how it went. Null before any.",
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "checkedAt": {
                "type": "string",
                "format": "date-time"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The store’s own words when `ok` is false."
              }
            }
          },
          "createdBy": {
            "type": "string",
            "description": "Who added it: a user, an API key, or SyncWords staff."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "StorageConnectionCreate": {
        "type": "object",
        "required": [
          "name",
          "bucket",
          "accessKeyId",
          "secretAccessKey"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Unique in your account."
          },
          "bucket": {
            "type": "string",
            "description": "The bucket’s name: 3 to 63 lowercase letters, digits, dots or hyphens."
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ],
            "description": "For an S3-compatible store: `https://…` with no path, reachable from the internet. Leave out, or null, for AWS S3."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "The signing region, when the store needs one. Leave out for `auto` with an endpoint, `us-east-1` without."
          },
          "accessKeyId": {
            "type": "string",
            "maxLength": 128
          },
          "secretAccessKey": {
            "type": "string",
            "maxLength": 256,
            "description": "Stored encrypted, never answered."
          }
        }
      },
      "StorageConnectionUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Any of the fields of a create; the rest keep their values.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "bucket": {
            "type": "string"
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "accessKeyId": {
            "type": "string",
            "maxLength": 128
          },
          "secretAccessKey": {
            "type": "string",
            "maxLength": 256
          }
        }
      },
      "AccountSettings": {
        "type": "object",
        "required": [
          "defaultStorageConnectionId"
        ],
        "additionalProperties": false,
        "properties": {
          "defaultStorageConnectionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The storage connection a project created without `storage` uses; null for SyncWords’ storage."
          }
        }
      },
      "ProjectStorageWrite": {
        "type": "object",
        "required": [
          "connectionId"
        ],
        "additionalProperties": false,
        "description": "Where the project keeps its input and output folders: one of your storage connections — your own bucket — with a folder for each, or `connectionId: null` for SyncWords’ storage. Left out on create, your account’s default decides; left out on a change, nothing moves. A project on your own bucket reads its input folder and writes its output folder: no feed source, no feed outputs.",
        "properties": {
          "connectionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "A connection from `GET /account/storage-connections`, or null."
          },
          "inputPrefix": {
            "type": "string",
            "maxLength": 512,
            "description": "The folder in the bucket the service watches for input files (`dubbing/in/`; a trailing slash is added). Default: `vod-express/<project id>/input/`. No other project of your account may watch this folder, or one inside it, on the same connection."
          },
          "outputPrefix": {
            "type": "string",
            "maxLength": 512,
            "description": "The folder results are written to. Default: `vod-express/<project id>/output/`. May not be the input folder or inside it."
          }
        }
      },
      "EnumValue": {
        "type": "object",
        "required": [
          "type",
          "title",
          "description"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "The `@type` to write."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProjectCreate": {
        "type": "object",
        "required": [
          "name",
          "config"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "description": "Your name for the project. Need not be unique."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "buildVersion": {
            "type": "string",
            "description": "The build to run on, from `GET /build-versions`. Left out, your account’s default."
          },
          "config": {
            "$ref": "#/components/schemas/Config",
            "description": "The processing instructions, validated against the schema of the project’s build. Shown here: build `0.9.0-alpha1-2026-10-20T14-10Z-3f05aa`; `GET /schema` has any build’s."
          },
          "startOnNewFiles": {
            "type": "boolean",
            "default": true,
            "description": "When the project is `stopped` and a new file lands in its input folder, start it: at once when an API upload completes, and within a minute when the project stopped itself after its last file and the file came another way (the AWS CLI, an SDK, a one-`PUT` upload). False: new files wait for `POST …/start`. Retrying a file starts the project either way. See Auto-stop."
          },
          "storage": {
            "$ref": "#/components/schemas/ProjectStorageWrite"
          },
          "options": {
            "$ref": "#/components/schemas/ProjectOptions"
          }
        }
      },
      "ProjectOptions": {
        "type": "object",
        "additionalProperties": false,
        "description": "Instructions for the create itself: acted on once, not stored, not returned. Refused on `PUT`/`PATCH`.",
        "properties": {
          "autoStart": {
            "type": "boolean",
            "default": true,
            "description": "Start the processing service now; false leaves the project `stopped` until `POST …/start`, so files can be uploaded first."
          }
        }
      },
      "ProjectUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "buildVersion": {
            "type": "string"
          },
          "config": {
            "$ref": "#/components/schemas/Config",
            "description": "The processing instructions, validated against the schema of the project’s build. Shown here: build `0.9.0-alpha1-2026-10-20T14-10Z-3f05aa`; `GET /schema` has any build’s."
          },
          "startOnNewFiles": {
            "type": "boolean",
            "description": "Whether a new file starts a stopped project; see `startOnNewFiles` on create."
          },
          "storage": {
            "$ref": "#/components/schemas/ProjectStorageWrite"
          }
        }
      },
      "Project": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "buildVersion",
          "status",
          "files",
          "startOnNewFiles",
          "config",
          "storage",
          "meta"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "A lowercase ULID; ids sort by creation time."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "buildVersion": {
            "type": "string",
            "description": "The build the project runs on, kept until you change it."
          },
          "status": {
            "enum": [
              "running",
              "stopped",
              "error"
            ],
            "description": "Whether the processing service is up. `error`: the last start failed, or the service stopped working (`pod` in `GET …/status`); `POST …/start` retries it."
          },
          "files": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "total",
              "pending",
              "processing",
              "done",
              "failed",
              "countedAt"
            ],
            "description": "How many input files are in each state, as last counted: the counts of `GET …/files`’ `meta`, kept on the project so a list can show every project’s progress. A project with a service, or changed in the last day, is counted once a minute; every change made through this API (an upload completed, a file deleted or retried, a files listing) counts again at once. A file copied in with the AWS CLI shows at the next minute. `null` until the first count. A project is finished when `pending` and `processing` are both 0.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              },
              "processing": {
                "type": "integer"
              },
              "done": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "countedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When they were counted."
              }
            }
          },
          "startOnNewFiles": {
            "type": "boolean",
            "description": "When the project is `stopped` and a new file lands in its input folder, start it: at once when an API upload completes, and within a minute when the project stopped itself after its last file and the file came another way (the AWS CLI, an SDK, a one-`PUT` upload). False: new files wait for `POST …/start`. Retrying a file starts the project either way. See Auto-stop."
          },
          "config": {
            "$ref": "#/components/schemas/Config",
            "description": "The processing instructions, validated against the schema of the project’s build. Shown here: build `0.9.0-alpha1-2026-10-20T14-10Z-3f05aa`; `GET /schema` has any build’s."
          },
          "storage": {
            "type": "object",
            "required": [
              "connection",
              "path",
              "inputPath",
              "outputPath",
              "inputPrefix",
              "outputPrefix"
            ],
            "description": "Where the project’s files are: SyncWords’ storage (`connection` null; the folder the upload role reaches), or your own bucket through a storage connection, with the project’s two folders in it.",
            "properties": {
              "connection": {
                "type": [
                  "object",
                  "null"
                ],
                "required": [
                  "id",
                  "name",
                  "bucket",
                  "endpoint"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "bucket": {
                    "type": "string"
                  },
                  "endpoint": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "path": {
                "type": "string",
                "description": "The project’s folder, or the folder of your bucket both others share."
              },
              "inputPath": {
                "type": "string",
                "description": "Upload here."
              },
              "outputPath": {
                "type": "string",
                "description": "Outputs appear here."
              },
              "inputPrefix": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The input folder in your bucket, as `storage` takes it; null for SyncWords’ storage."
              },
              "outputPrefix": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "createdAt",
              "updatedAt",
              "createdBy",
              "configLocked",
              "messages"
            ],
            "properties": {
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              },
              "createdBy": {
                "type": "object",
                "required": [
                  "accountId",
                  "accountName",
                  "userId",
                  "userEmail",
                  "userFullName"
                ],
                "description": "The owning account, and the user who created the project; null fields for an organization token, or a deleted account or user.",
                "properties": {
                  "accountId": {
                    "type": "integer"
                  },
                  "accountName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "userId": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "userEmail": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "userFullName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "configLocked": {
                "type": "boolean",
                "description": "True while SyncWords support manages the project’s configuration for you: `config` and `buildVersion` cannot be changed (`409 project_config_locked`) until support hands it back. `name` and `description` can be changed as usual."
              },
              "messages": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Message"
                },
                "description": "What the config will do that you probably do not mean, such as a dub no output carries. Empty when there is nothing to say."
              }
            }
          }
        }
      },
      "ProjectPage": {
        "type": "object",
        "required": [
          "data",
          "links",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          },
          "links": {
            "type": "object",
            "description": "Absolute URLs that keep the request’s filters and `perPage`.",
            "required": [
              "first",
              "last",
              "prev",
              "next"
            ],
            "properties": {
              "first": {
                "type": "string"
              },
              "last": {
                "type": "string"
              },
              "prev": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Null on the last page."
              }
            }
          },
          "meta": {
            "type": "object",
            "description": "snake_case, the one exception to the API’s camelCase.",
            "required": [
              "current_page",
              "from",
              "last_page",
              "links",
              "path",
              "per_page",
              "to",
              "total"
            ],
            "properties": {
              "current_page": {
                "type": "integer"
              },
              "from": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "last_page": {
                "type": "integer"
              },
              "links": {
                "type": "array",
                "description": "A pagination bar: previous, the page numbers (`...` for a gap, with a null `url`), next. Labels are HTML (`&laquo; Previous`).",
                "items": {
                  "type": "object",
                  "required": [
                    "url",
                    "label",
                    "active"
                  ],
                  "properties": {
                    "url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "active": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "path": {
                "type": "string",
                "description": "The listing’s URL, without a query."
              },
              "per_page": {
                "type": "integer"
              },
              "to": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      },
      "SourceFeed": {
        "type": "object",
        "required": [
          "url",
          "sourceType",
          "checkedAt",
          "state",
          "problem",
          "httpStatus",
          "items",
          "usableItems",
          "skippedItems"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "sourceType": {
            "enum": [
              "RssSource",
              "MrssSource"
            ],
            "description": "How the feed was read: each item’s `<enclosure>` (`RssSource`) or `<media:content>` (`MrssSource`)."
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "enum": [
              "ok",
              "partial",
              "empty",
              "no_usable_items",
              "unreachable",
              "not_a_feed",
              "unchecked"
            ],
            "description": "`ok`: every item links a media file the project downloads. `partial`: some do; `skippedItems` lists the others. `empty`: no items yet. `no_usable_items`: none does, so nothing is processed. `unreachable`: no `2xx` answer, after up to 5 redirects (never from HTTPS to HTTP). `not_a_feed`: the answer is not an RSS 2.0 document. `unchecked`: larger than 20 MB, which the check does not read."
          },
          "problem": {
            "type": [
              "string",
              "null"
            ],
            "description": "What is wrong, in a sentence; null for `ok` and `empty`."
          },
          "httpStatus": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The feed’s HTTP status; null when it did not answer."
          },
          "items": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null when the feed could not be read."
          },
          "usableItems": {
            "type": [
              "integer",
              "null"
            ]
          },
          "skippedItems": {
            "type": "array",
            "description": "The first 50 items the project skips, and why.",
            "items": {
              "type": "object",
              "required": [
                "guid",
                "title",
                "link",
                "reason"
              ],
              "properties": {
                "guid": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "link": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reason": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ServiceStatus": {
        "type": "object",
        "required": [
          "status",
          "orchestratorStatus",
          "serviceId",
          "launchedAt",
          "version",
          "projectStatus",
          "lastError",
          "pod"
        ],
        "properties": {
          "status": {
            "enum": [
              "ACTIVE",
              "ERROR",
              "NOT_RUNNING"
            ],
            "description": "`ACTIVE`: up. `NOT_RUNNING`: stopped. `ERROR`: it did not start, or started and stopped working (see `pod`); `POST …/start` replaces it."
          },
          "orchestratorStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "The processing platform’s own word for the service’s state. Informational: branch on `status`."
          },
          "serviceId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The running service’s id; a new one at every start."
          },
          "launchedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "version": {
            "type": [
              "string",
              "null"
            ],
            "description": "The build the running service runs."
          },
          "projectStatus": {
            "enum": [
              "running",
              "stopped",
              "error"
            ]
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set while `projectStatus` is `error` because the last start failed; `POST …/start` retries it. Null when the service started and then stopped working: `pod` says so."
          },
          "pod": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "status",
              "message",
              "at"
            ],
            "description": "The last state reported by the service’s process: `ready`, `notready`, or `failed` (not running 15 minutes after the start). Null until one is known.",
            "properties": {
              "status": {
                "type": "string"
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Reserved; null."
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "description": "When the status was reported; for `failed`, when it first failed. A service that has read `failed` for two hours is stopped."
              }
            }
          }
        }
      },
      "InputFile": {
        "type": "object",
        "required": [
          "name",
          "size",
          "uploadedAt",
          "status",
          "hash",
          "outputs",
          "failureReason"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The file’s path under `input/`: `season 2/episode 01.mp4` for a file in a subfolder."
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          },
          "uploadedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "enum": [
              "pending",
              "processing",
              "done",
              "failed"
            ],
            "description": "`pending`: waiting, or being transcribed, translated and dubbed (most of a file’s time); also a retried file until its new run gets that far, and a replaced one (`stale`). `processing`: the outputs are being written. `done`: every output is written. `failed`: see `failureCode`, and retry."
          },
          "hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "The file’s processing key, the same for every run of this name in the project. Quote it to support."
          },
          "outputs": {
            "type": "array",
            "description": "Every output the config produces for this input.",
            "items": {
              "type": "object",
              "required": [
                "path",
                "available",
                "size"
              ],
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Its path under `output/`."
                },
                "available": {
                  "type": "boolean",
                  "description": "Written yet."
                },
                "size": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set on a `failed` file: that it failed and can be retried."
          },
          "failureCode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "no_audio",
              "not_media",
              "voice_not_found",
              "interrupted",
              "internal",
              null
            ],
            "description": "Set on a `failed` file: why. `no_audio`: the file has no audio track, so there is nothing to dub. `not_media`: the file is not media, or is truncated; upload it again under a new name. `voice_not_found`: the config names a voice the engine does not have; correct it and retry. `interrupted`: processing was stopped before it finished (the worker was replaced, or the project stopped); retry the file. `internal`: a problem on our side; retry once, then contact support."
          },
          "stale": {
            "type": "boolean",
            "description": "The input was replaced (copied over with the AWS CLI) after it was processed: `status` is `pending`, and the outputs are still the old content’s. Retry the file to process the new content."
          },
          "usage": {
            "type": [
              "array",
              "null"
            ],
            "description": "What a `done` file was billed for: one entry for its captions in the source language, one translation per target language, and one dub per dubbed language, each by the input’s length. Retrying the same content bills nothing new, so a retried file keeps its first run’s entries; a replaced file is billed again. `null` for a file that is not `done`, and for up to a minute after it finishes, until it is recorded.",
            "items": {
              "type": "object",
              "required": [
                "kind",
                "language",
                "durationMs"
              ],
              "properties": {
                "kind": {
                  "enum": [
                    "captioning",
                    "translation",
                    "dubbing"
                  ]
                },
                "language": {
                  "type": "string",
                  "description": "The source language for `captioning`, the target language otherwise."
                },
                "durationMs": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "The input’s length in milliseconds, which the entry is billed by. `null` while processing has not reported a length; the entry is billed once it has."
                }
              }
            }
          }
        }
      },
      "FileCounts": {
        "type": "object",
        "description": "How many of the project’s files are in each state.",
        "required": [
          "total",
          "pending",
          "processing",
          "done",
          "failed"
        ],
        "properties": {
          "total": {
            "type": "integer"
          },
          "pending": {
            "type": "integer"
          },
          "processing": {
            "type": "integer"
          },
          "done": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        }
      },
      "OutputFile": {
        "type": "object",
        "required": [
          "path",
          "size",
          "lastModified"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "Its path under `output/`."
          },
          "size": {
            "type": "integer"
          },
          "lastModified": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Upload": {
        "type": "object",
        "required": [
          "uploadId",
          "key",
          "partSize",
          "partCount"
        ],
        "properties": {
          "uploadId": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "description": "The file’s name under `input/`, which every later call of the upload sends back."
          },
          "partSize": {
            "type": "integer",
            "description": "Bytes a part: 16 MiB, or more for a file that would need over 10,000 parts."
          },
          "partCount": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "description": "A presigned S3 URL to `PUT` the whole file to, instead of the parts. Present when the file is 5 GB or under and no file of that name is in `input/`."
          },
          "urlExpiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When `url` stops working: an hour from now at most, sooner when the credentials that signed it expire first. Present with `url`."
          }
        }
      },
      "DownloadLink": {
        "type": "object",
        "required": [
          "url",
          "expiresAt"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "A presigned S3 URL."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the URL stops working: an hour from now at most, sooner when the credentials that signed it expire first."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "file.done",
                "file.failed",
                "project.stopped",
                "project.error"
              ]
            }
          },
          "createdAt": {
            "type": "string"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "eventId",
          "type",
          "status",
          "attempts",
          "createdAt"
        ],
        "properties": {
          "eventId": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ],
            "description": "`pending`: not delivered yet, and still tried. `failed`: given up, 24 hours after the event (or after `POST …/retry`)."
          },
          "attempts": {
            "type": "integer"
          },
          "lastStatus": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Your server’s last HTTP status; null when it could not be reached."
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the last attempt failed."
          },
          "lastAttemptAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "nextAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "While `pending`: when it is tried next."
          },
          "deliveredAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "When the event happened."
          }
        }
      },
      "WebhookDeliveryPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "links": {
            "type": "object",
            "properties": {
              "first": {
                "type": "string"
              },
              "last": {
                "type": "string"
              },
              "prev": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Null on the last page."
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "current_page": {
                "type": "integer"
              },
              "from": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "last_page": {
                "type": "integer"
              },
              "per_page": {
                "type": "integer"
              },
              "to": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      },
      "WebhookTestResult": {
        "type": "object",
        "required": [
          "eventId",
          "delivered",
          "status",
          "error",
          "durationMs"
        ],
        "properties": {
          "eventId": {
            "type": "string"
          },
          "delivered": {
            "type": "boolean",
            "description": "Your server answered 2xx."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMs": {
            "type": "integer"
          }
        }
      },
      "FileDoneEvent": {
        "type": "object",
        "description": "A file finished processing. Download the outputs whose `available` is true.",
        "required": [
          "id",
          "type",
          "timestamp",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event’s id, `evt_…`: the same on every attempt, so drop an id you have handled. Also in the `webhook-id` header."
          },
          "type": {
            "type": "string",
            "enum": [
              "file.done"
            ]
          },
          "timestamp": {
            "type": "string",
            "description": "When it happened (ISO 8601, UTC)."
          },
          "data": {
            "type": "object",
            "required": [
              "project",
              "file"
            ],
            "properties": {
              "project": {
                "type": "object",
                "required": [
                  "id",
                  "name"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "file": {
                "type": "object",
                "required": [
                  "name",
                  "hash",
                  "outputs",
                  "completedAt"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The input’s path under `input/`."
                  },
                  "hash": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The file’s processing key: the same for every run of this name in the project, retries and replacements included, so it does not tell runs apart. Quote it to support. To tell events apart use the event `id`; runs, `completedAt`."
                  },
                  "outputs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "path",
                        "available",
                        "size"
                      ],
                      "properties": {
                        "path": {
                          "type": "string",
                          "description": "Under `output/`: what `GET …/outputs/download?path=` takes."
                        },
                        "available": {
                          "type": "boolean"
                        },
                        "size": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      }
                    }
                  },
                  "completedAt": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "FileFailedEvent": {
        "type": "object",
        "description": "A file could not be processed. `POST /projects/{id}/files/{name}/retry` tries it again.",
        "required": [
          "id",
          "type",
          "timestamp",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event’s id, `evt_…`: the same on every attempt, so drop an id you have handled. Also in the `webhook-id` header."
          },
          "type": {
            "type": "string",
            "enum": [
              "file.failed"
            ]
          },
          "timestamp": {
            "type": "string",
            "description": "When it happened (ISO 8601, UTC)."
          },
          "data": {
            "type": "object",
            "required": [
              "project",
              "file"
            ],
            "properties": {
              "project": {
                "type": "object",
                "required": [
                  "id",
                  "name"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "file": {
                "type": "object",
                "required": [
                  "name",
                  "failureReason",
                  "failedAt"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "failureReason": {
                    "type": "string"
                  },
                  "failedAt": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "ProjectStoppedEvent": {
        "type": "object",
        "description": "A running project stopped: on request (`POST …/stop`), idle after its last file (auto-stop), or deleted.",
        "required": [
          "id",
          "type",
          "timestamp",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event’s id, `evt_…`: the same on every attempt, so drop an id you have handled. Also in the `webhook-id` header."
          },
          "type": {
            "type": "string",
            "enum": [
              "project.stopped"
            ]
          },
          "timestamp": {
            "type": "string",
            "description": "When it happened (ISO 8601, UTC)."
          },
          "data": {
            "type": "object",
            "required": [
              "project",
              "reason"
            ],
            "properties": {
              "project": {
                "type": "object",
                "required": [
                  "id",
                  "name"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "reason": {
                "type": "string",
                "enum": [
                  "requested",
                  "idle",
                  "deleted"
                ]
              }
            }
          }
        }
      },
      "ProjectErrorEvent": {
        "type": "object",
        "description": "The processing service did not start (`launch_failed`) or stopped working (`service_failed`). `POST /projects/{id}/start` tries again.",
        "required": [
          "id",
          "type",
          "timestamp",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event’s id, `evt_…`: the same on every attempt, so drop an id you have handled. Also in the `webhook-id` header."
          },
          "type": {
            "type": "string",
            "enum": [
              "project.error"
            ]
          },
          "timestamp": {
            "type": "string",
            "description": "When it happened (ISO 8601, UTC)."
          },
          "data": {
            "type": "object",
            "required": [
              "project",
              "reason",
              "message"
            ],
            "properties": {
              "project": {
                "type": "object",
                "required": [
                  "id",
                  "name"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              "reason": {
                "type": "string",
                "enum": [
                  "launch_failed",
                  "service_failed"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Config": {
        "type": "object",
        "additionalProperties": false,
        "title": "Config",
        "properties": {
          "sourceMedia": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Config.S3FolderSourceConfig"
              },
              {
                "$ref": "#/components/schemas/Config.RssSourceConfig"
              },
              {
                "$ref": "#/components/schemas/Config.MrssSourceConfig"
              }
            ],
            "discriminator": {
              "propertyName": "@type",
              "mapping": {
                "S3FolderSource": "#/components/schemas/Config.S3FolderSourceConfig",
                "RssSource": "#/components/schemas/Config.RssSourceConfig",
                "MrssSource": "#/components/schemas/Config.MrssSourceConfig"
              }
            },
            "description": "Where the media comes from: the project’s input folder (`S3FolderSource`, also what a config without one gets), an RSS feed (`RssSource`: the media in each item’s `<enclosure>`) or a Media RSS feed (`MrssSource`: each item’s `<media:content>`). Set it on create: a change between the folder and a feed is refused; a feed’s `url` and its kind may change.",
            "title": "Source Media"
          },
          "transcription": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Config.SpeechmaticsAsrTranscription"
              },
              {
                "$ref": "#/components/schemas/Config.SonioxAsrTranscription"
              },
              {
                "$ref": "#/components/schemas/Config.SarvamAsrTranscription"
              }
            ],
            "discriminator": {
              "propertyName": "@type",
              "mapping": {
                "Speechmatics": "#/components/schemas/Config.SpeechmaticsAsrTranscription",
                "Soniox": "#/components/schemas/Config.SonioxAsrTranscription",
                "Sarvam": "#/components/schemas/Config.SarvamAsrTranscription"
              }
            },
            "description": "What is spoken in the media, and the engine that transcribes it. `@type` picks the engine; left out, the first engine that transcribes `language` is used.",
            "title": "Transcription"
          },
          "translations": {
            "title": "Translations",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Config.Translation"
            },
            "description": "One entry per target language: its translation engine and, to dub it, `ttsOptions`. Left out or empty, the media is only transcribed."
          },
          "outputs": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Config.S3FolderOutputConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.RssOutputConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.MrssOutputConfig"
                }
              ],
              "discriminator": {
                "propertyName": "@type",
                "mapping": {
                  "S3FolderOutput": "#/components/schemas/Config.S3FolderOutputConfig",
                  "RssOutput": "#/components/schemas/Config.RssOutputConfig",
                  "MrssOutput": "#/components/schemas/Config.MrssOutputConfig"
                }
              }
            },
            "description": "Where the results go: the project’s output folder (`S3FolderOutput`), and for a feed project one re-published feed per language (`RssOutput` with an `RssSource`, `MrssOutput` with an `MrssSource`). Each lists its `items`, and each item is one file written for every input.",
            "title": "Outputs"
          }
        },
        "required": [
          "transcription",
          "outputs"
        ],
        "description": "A project’s processing instructions."
      },
      "Config.S3FolderSourceConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "Input folder",
        "properties": {
          "@type": {
            "default": "S3FolderSource",
            "const": "S3FolderSource",
            "type": "string",
            "enum": [
              "S3FolderSource"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "S3FolderSource"
          }
        },
        "description": "Files put into the project’s input folder (`storage.inputPath`), through the API or the AWS role; each is processed once."
      },
      "Config.RssSourceConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "RSS feed",
        "properties": {
          "url": {
            "type": "string",
            "description": "The feed: `https://` only, no credentials in it, on a host that resolves to a public address. A new URL is fetched and read when the project is saved.",
            "title": "Url"
          },
          "@type": {
            "default": "RssSource",
            "const": "RssSource",
            "type": "string",
            "enum": [
              "RssSource"
            ]
          }
        },
        "required": [
          "@type",
          "url"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "RssSource"
          }
        },
        "description": "An RSS 2.0 feed (a podcast feed, say), read every 30 seconds. Each item’s media is its `<enclosure>`, downloaded into the input folder, named after the item, and processed once; an item whose media URL changes is processed again. Files cannot be uploaded to such a project."
      },
      "Config.MrssSourceConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "Media RSS feed",
        "properties": {
          "url": {
            "type": "string",
            "description": "The feed: `https://` only, no credentials in it, on a host that resolves to a public address. A new URL is fetched and read when the project is saved.",
            "title": "Url"
          },
          "@type": {
            "default": "MrssSource",
            "const": "MrssSource",
            "type": "string",
            "enum": [
              "MrssSource"
            ]
          }
        },
        "required": [
          "@type",
          "url"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "MrssSource"
          }
        },
        "description": "A Media RSS feed, read every 30 seconds. Each item’s media is its `<media:content>` (the default one of a `<media:group>`), downloaded into the input folder, named after the item, and processed once; an item whose media URL changes is processed again. Files cannot be uploaded to such a project."
      },
      "Config.SpeechmaticsAsrTranscription": {
        "type": "object",
        "additionalProperties": false,
        "title": "Speechmatics",
        "properties": {
          "language": {
            "type": "string",
            "description": "The language spoken in the media, as a code from `GET /enums/languages` (`en`, `es`, `pt-BR`).",
            "title": "Language"
          },
          "outputLabel": {
            "default": null,
            "description": "The label written where an output shows one, such as a subtitle track's name. Default: the language's English name.",
            "title": "Output Label",
            "type": [
              "string",
              "null"
            ]
          },
          "diarization": {
            "type": "boolean",
            "default": true,
            "description": "Ignored: speakers are always told apart.",
            "title": "Diarization",
            "deprecated": true
          },
          "maxDelay": {
            "default": null,
            "description": "Seconds the recognizer may take to settle a word; longer is more accurate. Left out, it is chosen for you.",
            "title": "Max Delay",
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 4
          },
          "filterDisfluency": {
            "type": "boolean",
            "default": true,
            "description": "Drop \"uh\", \"um\" and similar from the transcript.",
            "title": "Filter Disfluency"
          },
          "filterProfanity": {
            "type": "string",
            "enum": [
              "keep",
              "remove"
            ],
            "default": "remove",
            "description": "`remove` takes profanity out of the transcript; `keep` leaves it in.",
            "title": "Filter Profanity"
          },
          "volumeThreshold": {
            "default": null,
            "description": "Ignore audio quieter than this, so background noise is not transcribed.",
            "title": "Volume Threshold",
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "punctuationSensitivity": {
            "default": null,
            "description": "How readily punctuation is added: lower is more conservative. Left out, the language’s own default (usually 0.5).",
            "title": "Punctuation Sensitivity",
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1
          },
          "previewModel": {
            "type": "boolean",
            "default": false,
            "description": "Use Speechmatics' preview model.",
            "title": "Preview Model"
          },
          "dictionary": {
            "default": null,
            "title": "Dictionary",
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "A dictionary of your account in the transcription language, by id or name: words the engine should recognise, such as names and terminology. Managed with the Live API (`/v2/dictionaries`). Left out, your account's default for the language applies, if it has one; `null` applies none."
          },
          "postprocessingDictionary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "integer",
                "title": "Postprocessing dictionary id"
              },
              {
                "type": "string",
                "title": "Postprocessing dictionary name"
              }
            ],
            "default": null,
            "description": "Word replacements applied to the transcript before translation, so a name is right in every language. The key is the word you want; the value lists what the engine writes instead, as literals or Java regular expressions. Matching ignores case and applies to single words. An empty key deletes the words it lists. Or a postprocessing dictionary of your account in the transcription language, by id or name, managed with the Live API (`/v2/postprocessing-dictionaries`). Left out, your account's default for the language applies, if it has one; `null` applies none.",
            "title": "Postprocessing Dictionary"
          },
          "expectedLanguages": {
            "title": "Expected Languages",
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "With `language: \"mul\"`, the languages to expect in the media, as codes."
          },
          "@type": {
            "default": "Speechmatics",
            "const": "Speechmatics",
            "type": "string",
            "enum": [
              "Speechmatics"
            ]
          }
        },
        "required": [
          "@type",
          "language"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Speechmatics"
          }
        },
        "description": "Broad language coverage; the default for most languages."
      },
      "Config.SonioxAsrTranscription": {
        "type": "object",
        "additionalProperties": false,
        "title": "Soniox",
        "properties": {
          "language": {
            "type": "string",
            "description": "The language spoken in the media, as a code from `GET /enums/languages` (`en`, `es`, `pt-BR`).",
            "title": "Language"
          },
          "model": {
            "type": "string",
            "default": "stt-rt-v5",
            "description": "The Soniox model.",
            "title": "Model"
          },
          "extraPrompt": {
            "default": null,
            "description": "Context that guides recognition: the topic, names, terminology.",
            "title": "Extra Prompt",
            "type": [
              "string",
              "null"
            ]
          },
          "outputLabel": {
            "default": null,
            "description": "The label written where an output shows one, such as a subtitle track's name. Default: the language's English name.",
            "title": "Output Label",
            "type": [
              "string",
              "null"
            ]
          },
          "diarization": {
            "type": "boolean",
            "default": true,
            "description": "Ignored: speakers are always told apart.",
            "title": "Diarization",
            "deprecated": true
          },
          "filterDisfluency": {
            "type": "boolean",
            "default": true,
            "description": "Drop \"uh\", \"um\" and similar from the transcript.",
            "title": "Filter Disfluency"
          },
          "dictionary": {
            "default": null,
            "title": "Dictionary",
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "A dictionary of your account in the transcription language, by id or name: words the engine should recognise, such as names and terminology. Managed with the Live API (`/v2/dictionaries`). Left out, your account's default for the language applies, if it has one; `null` applies none."
          },
          "postprocessingDictionary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "integer",
                "title": "Postprocessing dictionary id"
              },
              {
                "type": "string",
                "title": "Postprocessing dictionary name"
              }
            ],
            "default": null,
            "description": "Word replacements applied to the transcript before translation, so a name is right in every language. The key is the word you want; the value lists what the engine writes instead, as literals or Java regular expressions. Matching ignores case and applies to single words. An empty key deletes the words it lists. Or a postprocessing dictionary of your account in the transcription language, by id or name, managed with the Live API (`/v2/postprocessing-dictionaries`). Left out, your account's default for the language applies, if it has one; `null` applies none.",
            "title": "Postprocessing Dictionary"
          },
          "expectedLanguages": {
            "title": "Expected Languages",
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "With `language: \"mul\"`, the languages to expect in the media, as codes."
          },
          "@type": {
            "default": "Soniox",
            "const": "Soniox",
            "type": "string",
            "enum": [
              "Soniox"
            ]
          }
        },
        "required": [
          "@type",
          "language"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Soniox"
          }
        },
        "description": "Strong accuracy, and the engine for mixed-language media (`language: \"mul\"`)."
      },
      "Config.SarvamAsrTranscription": {
        "type": "object",
        "additionalProperties": false,
        "title": "Sarvam",
        "properties": {
          "language": {
            "type": "string",
            "description": "The language spoken in the media, as a code from `GET /enums/languages` (`en`, `es`, `pt-BR`).",
            "title": "Language"
          },
          "outputLabel": {
            "default": null,
            "description": "The label written where an output shows one, such as a subtitle track's name. Default: the language's English name.",
            "title": "Output Label",
            "type": [
              "string",
              "null"
            ]
          },
          "diarization": {
            "type": "boolean",
            "default": true,
            "description": "Ignored: speakers are always told apart.",
            "title": "Diarization",
            "deprecated": true
          },
          "filterDisfluency": {
            "type": "boolean",
            "default": true,
            "description": "Drop \"uh\", \"um\" and similar from the transcript.",
            "title": "Filter Disfluency"
          },
          "dictionary": {
            "default": null,
            "title": "Dictionary",
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "Not available for Sarvam."
          },
          "postprocessingDictionary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "integer",
                "title": "Postprocessing dictionary id"
              },
              {
                "type": "string",
                "title": "Postprocessing dictionary name"
              }
            ],
            "default": null,
            "description": "Word replacements applied to the transcript before translation, so a name is right in every language. The key is the word you want; the value lists what the engine writes instead, as literals or Java regular expressions. Matching ignores case and applies to single words. An empty key deletes the words it lists. Inline only: your account's postprocessing dictionaries apply to Speechmatics and Soniox.",
            "title": "Postprocessing Dictionary"
          },
          "model": {
            "type": "string",
            "default": "saaras:v4",
            "description": "The Sarvam model.",
            "title": "Model"
          },
          "prompt": {
            "default": null,
            "description": "Terminology hint applied to the final transcript.",
            "title": "Prompt",
            "options": {
              "additional": true
            },
            "type": [
              "string",
              "null"
            ]
          },
          "silenceDuration": {
            "type": "string",
            "pattern": "^[+-]?(([0-9]{1,2}:){0,2}[0-9]{1,12}([.,][0-9]{1,9})?|[.,][0-9]{1,9})$",
            "default": "0.3",
            "description": "The pause, in seconds, that ends an utterance. Shorter pauses give smaller, earlier chunks.",
            "title": "Silence Duration"
          },
          "expectedLanguages": {
            "title": "Expected Languages",
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "With `language: \"mul\"`, the languages to expect in the media, as codes."
          },
          "@type": {
            "default": "Sarvam",
            "const": "Sarvam",
            "type": "string",
            "enum": [
              "Sarvam"
            ]
          }
        },
        "required": [
          "@type",
          "language"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Sarvam"
          }
        },
        "description": "Specialised in Indian languages."
      },
      "Config.Translation": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "language": {
            "type": "string",
            "description": "The target language, as a code from `GET /enums/languages`.",
            "title": "Language"
          },
          "engine": {
            "title": "Engine",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Config.NoTranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.SonioxTranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.DeepLTranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.GoogleTranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.OpenAITranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.NovaTranslationProvider"
              },
              {
                "$ref": "#/components/schemas/Config.GeminiTranslationProvider"
              }
            ],
            "discriminator": {
              "propertyName": "@type",
              "mapping": {
                "NoTranslation": "#/components/schemas/Config.NoTranslationProvider",
                "Soniox": "#/components/schemas/Config.SonioxTranslationProvider",
                "DeepL": "#/components/schemas/Config.DeepLTranslationProvider",
                "Google": "#/components/schemas/Config.GoogleTranslationProvider",
                "OpenAI": "#/components/schemas/Config.OpenAITranslationProvider",
                "Nova": "#/components/schemas/Config.NovaTranslationProvider",
                "Gemini": "#/components/schemas/Config.GeminiTranslationProvider"
              }
            },
            "description": "The translation engine. Left out, the recommended engine for the language is used, and stored in the project."
          },
          "ttsOptions": {
            "title": "Tts Options",
            "$ref": "#/components/schemas/Config.Tts",
            "description": "Dub this language. Left out, the translation is used by subtitle outputs only."
          },
          "outputLabel": {
            "default": null,
            "description": "The label written where an output shows one. Default: the language's English name.",
            "title": "Output Label",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "language"
        ],
        "title": "Translation",
        "description": "One target language."
      },
      "Config.NoTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "NoTranslation",
        "properties": {
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "@type": {
            "default": "NoTranslation",
            "const": "NoTranslation",
            "type": "string",
            "enum": [
              "NoTranslation"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "NoTranslation"
          }
        },
        "description": "No machine translation: the source text is passed through."
      },
      "Config.DeepLTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "DeepL",
        "properties": {
          "useContext": {
            "type": "boolean",
            "default": true,
            "description": "Ignored: always on.",
            "title": "Use Context",
            "deprecated": true,
            "options": {
              "additional": true
            }
          },
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "@type": {
            "default": "DeepL",
            "const": "DeepL",
            "type": "string",
            "enum": [
              "DeepL"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "DeepL"
          }
        },
        "description": "Fast and literal; excellent for European languages."
      },
      "Config.GoogleTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Google Translate",
        "properties": {
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "@type": {
            "default": "Google",
            "const": "Google",
            "type": "string",
            "enum": [
              "Google"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Google"
          }
        },
        "description": "Google Translate: the widest language coverage."
      },
      "Config.GeminiTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Gemini",
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "Gemini 3.1 Flash Lite",
              "Gemini 3.1 Flash Lite (Thinking)"
            ],
            "default": "Gemini 3.1 Flash Lite",
            "title": "Model",
            "description": "The model."
          },
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "extraPrompt": {
            "type": "string",
            "default": "Avoid profane language in translated output.",
            "description": "Instructions for the translator: style, register, audience. They replace the default, so restate it to keep it.",
            "title": "Extra Prompt"
          },
          "latencyMode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "quality",
              "low-latency",
              null
            ],
            "default": null,
            "description": "`quality` translates whole sentences; `low-latency` smaller units. Left out, it is chosen to suit the transcription engine.",
            "title": "Latency Mode",
            "options": {
              "additional": true
            }
          },
          "compression": {
            "type": "boolean",
            "default": true,
            "description": "Condense long translations, which keeps a dub in step with the original speech.",
            "title": "Compression",
            "options": {
              "additional": true
            }
          },
          "enableSpeakerGenderAware": {
            "type": "boolean",
            "default": true,
            "description": "Deprecated by the build; kept so older configs stay valid.",
            "title": "Enable Speaker Gender Aware",
            "deprecated": true
          },
          "@type": {
            "default": "Gemini",
            "const": "Gemini",
            "type": "string",
            "enum": [
              "Gemini"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Gemini"
          }
        },
        "description": "Google Gemini: natural, context-aware translation. A strong default for dubbing."
      },
      "Config.Tts": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "provider": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Config.AmazonTtsProvider"
              },
              {
                "$ref": "#/components/schemas/Config.AzureTtsProvider"
              },
              {
                "$ref": "#/components/schemas/Config.DeepdubTtsProvider"
              },
              {
                "$ref": "#/components/schemas/Config.GoogleTtsProvider"
              },
              {
                "$ref": "#/components/schemas/Config.MultiVoiceTtsProvider"
              },
              {
                "$ref": "#/components/schemas/Config.SarvamTtsProvider"
              }
            ],
            "discriminator": {
              "propertyName": "@type",
              "mapping": {
                "Amazon": "#/components/schemas/Config.AmazonTtsProvider",
                "Azure": "#/components/schemas/Config.AzureTtsProvider",
                "Deepdub": "#/components/schemas/Config.DeepdubTtsProvider",
                "Google": "#/components/schemas/Config.GoogleTtsProvider",
                "MultiVoice": "#/components/schemas/Config.MultiVoiceTtsProvider",
                "Sarvam": "#/components/schemas/Config.SarvamTtsProvider"
              }
            },
            "description": "The voice provider and the voice.",
            "title": "Provider"
          },
          "locale": {
            "default": null,
            "description": "The pronunciation locale, such as `es-XL` (Latin American Spanish) or `pt-BR`. It must belong to the translation's language. Left out, it is chosen from the language.",
            "title": "Locale",
            "type": [
              "string",
              "null"
            ]
          },
          "volume": {
            "type": "number",
            "minimum": 0,
            "maximum": 2,
            "default": 1,
            "description": "Level of the new voice.",
            "title": "Volume"
          },
          "originalAudioSplitting": {
            "type": "boolean",
            "default": true,
            "description": "`true` separates the original into voices and background, keeps the background and removes the original voices: a clean dub. `false` keeps the whole original under the new voice, at `originalAudioVolume`: a voice-over.",
            "title": "Original Audio Splitting"
          },
          "originalAudioVolume": {
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 1,
            "description": "Level of what is kept of the original: the background, or the whole original for a voice-over. `0` leaves it out; `1` is its own level.",
            "title": "Original Audio Volume"
          },
          "lateUtteranceTimeout": {
            "type": "string",
            "pattern": "^[+-]?(([0-9]{1,2}:){0,2}[0-9]{1,12}([.,][0-9]{1,9})?|[.,][0-9]{1,9})$",
            "default": "10",
            "description": "Seconds after which a line that could not be synthesised in time is skipped.",
            "title": "Late Utterance Timeout"
          },
          "dictionary": {
            "default": null,
            "description": "How words are spoken: `{\"SyncWords\": \"sink words\"}` has \"SyncWords\" said as \"sink words\".",
            "title": "Dictionary",
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "provider"
        ],
        "title": "Dubbing",
        "description": "How a translation is dubbed: the voice, and how the new voice is mixed with the original."
      },
      "Config.AmazonTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Amazon Polly",
        "properties": {
          "voice": {
            "type": "string",
            "description": "The voice, by its id or name in the provider's catalogue. It must speak the target language. Only a Deepdub voice is checked when the project is saved; for Amazon, Azure and Google, `GET /voices` lists known ones. See [Choosing a voice](/vod-express/configuration/voices).",
            "title": "Voice"
          },
          "engine": {
            "type": "string",
            "enum": [
              "STANDARD",
              "NEURAL",
              "GENERATIVE"
            ],
            "default": "NEURAL",
            "description": "The Polly engine; not every voice has every engine.",
            "title": "Engine"
          },
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "Amazon",
            "const": "Amazon",
            "type": "string",
            "enum": [
              "Amazon"
            ]
          }
        },
        "required": [
          "@type",
          "voice"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Amazon"
          }
        },
        "description": "Amazon Polly voices, by name. `GET /voices?provider=Amazon` lists known ones."
      },
      "Config.AzureTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Azure",
        "properties": {
          "voice": {
            "type": "string",
            "description": "The voice, by its id or name in the provider's catalogue. It must speak the target language. Only a Deepdub voice is checked when the project is saved; for Amazon, Azure and Google, `GET /voices` lists known ones. See [Choosing a voice](/vod-express/configuration/voices).",
            "title": "Voice"
          },
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "Azure",
            "const": "Azure",
            "type": "string",
            "enum": [
              "Azure"
            ]
          }
        },
        "required": [
          "@type",
          "voice"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Azure"
          }
        },
        "description": "Microsoft Azure neural voices, by name. `GET /voices?provider=Azure` lists known ones."
      },
      "Config.DeepdubTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Deepdub",
        "properties": {
          "voice": {
            "type": "string",
            "description": "Always `auto`, which clones each speaker's own voice; anything else is refused.",
            "title": "Voice"
          },
          "model": {
            "type": "string",
            "default": "syncwords34-baked",
            "description": "The Deepdub model.",
            "title": "Model",
            "options": {
              "additional": true
            }
          },
          "backupModel": {
            "type": "string",
            "default": "dd-etts-3.4",
            "description": "The model used when `model` is unavailable.",
            "title": "Backup Model",
            "options": {
              "additional": true
            }
          },
          "promptBoost": {
            "type": "boolean",
            "default": false,
            "description": "Work harder to reproduce each speaker's characteristics; less robust.",
            "title": "Prompt Boost"
          },
          "cleanMode": {
            "type": "boolean",
            "default": false,
            "description": "More robust with noisy source audio; somewhat the opposite of `promptBoost`. Not every model has it.",
            "title": "Clean Mode"
          },
          "referenceBufferDuration": {
            "type": "string",
            "pattern": "^[+-]?(([0-9]{1,2}:){0,2}[0-9]{1,12}([.,][0-9]{1,9})?|[.,][0-9]{1,9})$",
            "default": "2",
            "description": "Seconds of each speaker's audio used as the reference for cloning.",
            "title": "Reference Buffer Duration"
          },
          "accentLocale": {
            "default": null,
            "description": "Blend in an accent: a Deepdub locale, mixed in by `accentRatio`.",
            "title": "Accent Locale",
            "type": [
              "string",
              "null"
            ]
          },
          "accentRatio": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 0.75,
            "description": "How much of `accentLocale` to blend in: 0 is fully native, 1 fully the accent.",
            "title": "Accent Ratio"
          },
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "Deepdub",
            "const": "Deepdub",
            "type": "string",
            "enum": [
              "Deepdub"
            ]
          }
        },
        "required": [
          "@type",
          "voice"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Deepdub"
          }
        },
        "description": "Clones each speaker's own voice into the target language."
      },
      "Config.GoogleTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Google Text-to-Speech",
        "properties": {
          "voice": {
            "type": "string",
            "description": "The voice, by its id or name in the provider's catalogue. It must speak the target language. Only a Deepdub voice is checked when the project is saved; for Amazon, Azure and Google, `GET /voices` lists known ones. See [Choosing a voice](/vod-express/configuration/voices).",
            "title": "Voice"
          },
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "Google",
            "const": "Google",
            "type": "string",
            "enum": [
              "Google"
            ]
          }
        },
        "required": [
          "@type",
          "voice"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Google"
          }
        },
        "description": "Google Cloud Text-to-Speech voices, by name. `GET /voices?provider=Google` lists known ones."
      },
      "Config.MultiVoiceTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "MultiVoice",
        "properties": {
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "MultiVoice",
            "const": "MultiVoice",
            "type": "string",
            "enum": [
              "MultiVoice"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "MultiVoice"
          }
        },
        "description": "A voice for each speaker, chosen for you from SyncWords' voices for the translation's language."
      },
      "Config.SarvamTtsProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Sarvam voice cloning",
        "properties": {
          "rate": {
            "type": "integer",
            "minimum": 20,
            "maximum": 200,
            "default": 100,
            "description": "Speaking rate in percent, used with `rateStrategy: \"static\"`.",
            "title": "Rate"
          },
          "rateStrategy": {
            "type": "string",
            "enum": [
              "static",
              "dynamic"
            ],
            "default": "dynamic",
            "description": "`dynamic` speeds each line up or slows it down to fit the time the original took; `static` keeps `rate`.",
            "title": "Rate Strategy"
          },
          "minDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 80,
            "description": "The slowest `dynamic` may go, in percent.",
            "title": "Min Dynamic Rate"
          },
          "maxDynamicRate": {
            "type": "integer",
            "minimum": 50,
            "maximum": 200,
            "default": 160,
            "description": "The fastest `dynamic` may go, in percent.",
            "title": "Max Dynamic Rate"
          },
          "@type": {
            "default": "Sarvam",
            "const": "Sarvam",
            "type": "string",
            "enum": [
              "Sarvam"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Sarvam"
          }
        },
        "description": "Voice cloning for Indian languages."
      },
      "Config.S3FolderOutputConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "Output folder",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Config.VideoFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.AudioFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.SubtitleFileConfig"
                }
              ],
              "discriminator": {
                "propertyName": "@type",
                "mapping": {
                  "VideoFile": "#/components/schemas/Config.VideoFileConfig",
                  "AudioFile": "#/components/schemas/Config.AudioFileConfig",
                  "SubtitleFile": "#/components/schemas/Config.SubtitleFileConfig"
                }
              }
            },
            "description": "The files written to the output folder for every input.",
            "title": "Items"
          },
          "@type": {
            "default": "S3FolderOutput",
            "const": "S3FolderOutput",
            "type": "string",
            "enum": [
              "S3FolderOutput"
            ]
          }
        },
        "required": [
          "@type",
          "items"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "S3FolderOutput"
          }
        },
        "description": "The project’s output folder (`storage.outputPath`). One per project; its items’ names must differ."
      },
      "Config.VideoFileConfig": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "containerFormat": {
            "type": "string",
            "default": "mp4",
            "description": "The container, as an ffmpeg muxer name. It may differ from `ext`: `matroska` writes `.mkv`.",
            "title": "Container Format"
          },
          "ext": {
            "type": "string",
            "description": "The file extension: `${ext}` in `nameTemplate`.",
            "title": "Ext"
          },
          "nameTemplate": {
            "type": "string",
            "description": "The file's name. `${name}` is the input's name without its extension, subfolders included; `${ext}` is `ext`. It must contain `${name}`, and no two items of an output may render the same name.",
            "title": "Name Template"
          },
          "useOriginalTracks": {
            "type": "boolean",
            "default": false,
            "description": "`true` copies the input's audio into the file as it is, with its own language, title and default flag, ahead of the dub tracks.",
            "title": "Use Original Tracks"
          },
          "dubTracks": {
            "title": "Dub Tracks",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Config.DubTrackConfig"
            },
            "description": "The dubbed audio tracks, in order, after the input’s audio. With neither, the video has no sound."
          },
          "subTracks": {
            "title": "Sub Tracks",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Config.SubtitleTrackConfig"
            },
            "description": "Subtitle streams in the file, in order, which players can select."
          },
          "videoCodec": {
            "type": "string",
            "default": "copy",
            "description": "`copy` keeps the input's video untouched, the fastest. An ffmpeg encoder (`libx264`) re-encodes it.",
            "title": "Video Codec"
          },
          "@type": {
            "default": "VideoFile",
            "const": "VideoFile",
            "type": "string",
            "enum": [
              "VideoFile"
            ]
          }
        },
        "title": "Video file",
        "required": [
          "@type",
          "ext",
          "nameTemplate"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "VideoFile"
          }
        },
        "description": "The input's video with the audio and subtitle tracks you choose."
      },
      "Config.DubTrackConfig": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "language": {
            "type": "string",
            "description": "The code of a dubbed translation (one with `ttsOptions`), written as in `translations`.",
            "title": "Language"
          },
          "codec": {
            "type": "string",
            "default": "copy",
            "description": "`copy` keeps the dub's AAC. An ffmpeg encoder re-encodes it: `libmp3lame` for MP3.",
            "title": "Codec"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "The track name players show in their audio menu.",
            "title": "Title"
          }
        },
        "required": [
          "language"
        ],
        "title": "Dub track",
        "description": "One dubbed audio track of a video or audio file."
      },
      "Config.SubtitleTrackConfig": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "language": {
            "type": "string",
            "description": "The transcription language or a translation language.",
            "title": "Language"
          },
          "options": {
            "default": null,
            "description": "Caption layout options, passed to the caption renderer as given.",
            "title": "Options"
          },
          "subtitleCodec": {
            "type": "string",
            "default": "mov_text",
            "description": "`mov_text` for MP4 and MOV, `subrip` or `ass` for Matroska, `webvtt` for WebM.",
            "title": "Subtitle Codec"
          }
        },
        "required": [
          "language"
        ],
        "title": "Subtitle track",
        "description": "A subtitle stream inside the video, which players can select."
      },
      "Config.AudioFileConfig": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "containerFormat": {
            "type": "string",
            "description": "The container, as an ffmpeg muxer name: `mp3`, `ipod` (for `.m4a`), `wav`, …",
            "title": "Container Format"
          },
          "ext": {
            "type": "string",
            "description": "The file extension: `${ext}` in `nameTemplate`.",
            "title": "Ext"
          },
          "nameTemplate": {
            "type": "string",
            "description": "The file's name. `${name}` is the input's name without its extension, subfolders included; `${ext}` is `ext`. It must contain `${name}`, and no two items of an output may render the same name.",
            "title": "Name Template"
          },
          "useOriginalTracks": {
            "type": "boolean",
            "default": false,
            "description": "`true` makes the file the input's audio, copied as it is: AAC in most inputs, so `ipod` (`.m4a`) or `matroska`, not MP3 or WAV. Either this or one dub track.",
            "title": "Use Original Tracks"
          },
          "dubTracks": {
            "title": "Dub Tracks",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Config.DubTrackConfig"
            },
            "description": "The dub the file holds: exactly one entry, or none with `useOriginalTracks`."
          },
          "@type": {
            "default": "AudioFile",
            "const": "AudioFile",
            "type": "string",
            "enum": [
              "AudioFile"
            ]
          }
        },
        "title": "Audio file",
        "required": [
          "@type",
          "containerFormat",
          "ext",
          "nameTemplate"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "AudioFile"
          }
        },
        "description": "An audio file: MP3, M4A, WAV, …"
      },
      "Config.SubtitleFileConfig": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "format": {
            "type": "string",
            "description": "The caption format: `SRT`, `VTT`, `TXT`, … Also the file's extension, in lower case.",
            "title": "Format"
          },
          "language": {
            "type": "string",
            "description": "The transcription language or a translation language.",
            "title": "Language"
          },
          "nameTemplate": {
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "`${name}`, `${lang}` (the file's language) and `${ext}`. Default: `${name}.${lang}.${ext}`.",
            "title": "Name Template"
          },
          "options": {
            "default": null,
            "description": "Caption layout options, passed to the caption renderer as given.",
            "title": "Options"
          },
          "@type": {
            "default": "SubtitleFile",
            "const": "SubtitleFile",
            "type": "string",
            "enum": [
              "SubtitleFile"
            ]
          }
        },
        "title": "Subtitle file",
        "required": [
          "@type",
          "format",
          "language"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "SubtitleFile"
          }
        },
        "description": "A subtitle or transcript file in one language."
      },
      "Config.RssOutputConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "Output RSS feed",
        "properties": {
          "language": {
            "type": "string",
            "description": "The feed’s language: the transcription language or a translation language. Written to `<channel><language>`.",
            "title": "Language"
          },
          "items": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Config.VideoFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.AudioFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.SubtitleFileConfig"
                }
              ],
              "discriminator": {
                "propertyName": "@type",
                "mapping": {
                  "VideoFile": "#/components/schemas/Config.VideoFileConfig",
                  "AudioFile": "#/components/schemas/Config.AudioFileConfig",
                  "SubtitleFile": "#/components/schemas/Config.SubtitleFileConfig"
                }
              }
            },
            "description": "Exactly one video or audio file, which each feed item links to; other items are written beside it (under `output/feeds/{language}/`) and not linked yet.",
            "title": "Items"
          },
          "@type": {
            "default": "RssOutput",
            "const": "RssOutput",
            "type": "string",
            "enum": [
              "RssOutput"
            ]
          }
        },
        "required": [
          "@type",
          "language",
          "items"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "RssOutput"
          }
        },
        "description": "The source RSS feed re-published in one language, at `GET /feeds/{id}/{language}/feed.xml`: every item whose media is done links the dubbed file in its `<enclosure>` instead, `<channel><language>` is set, and the rest of the feed passes through. Items not done yet are left out. Needs an `RssSource`; one per language."
      },
      "Config.MrssOutputConfig": {
        "type": "object",
        "additionalProperties": false,
        "title": "Output Media RSS feed",
        "properties": {
          "language": {
            "type": "string",
            "description": "The feed’s language: the transcription language or a translation language. Written to `<channel><language>`.",
            "title": "Language"
          },
          "items": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Config.VideoFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.AudioFileConfig"
                },
                {
                  "$ref": "#/components/schemas/Config.SubtitleFileConfig"
                }
              ],
              "discriminator": {
                "propertyName": "@type",
                "mapping": {
                  "VideoFile": "#/components/schemas/Config.VideoFileConfig",
                  "AudioFile": "#/components/schemas/Config.AudioFileConfig",
                  "SubtitleFile": "#/components/schemas/Config.SubtitleFileConfig"
                }
              }
            },
            "description": "Exactly one video or audio file, which each feed item links to; other items are written beside it (under `output/feeds/{language}/`) and not linked yet.",
            "title": "Items"
          },
          "@type": {
            "default": "MrssOutput",
            "const": "MrssOutput",
            "type": "string",
            "enum": [
              "MrssOutput"
            ]
          }
        },
        "required": [
          "@type",
          "language",
          "items"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "MrssOutput"
          }
        },
        "description": "The source Media RSS feed re-published in one language, at `GET /feeds/{id}/{language}/feed.xml`: every item whose media is done links the dubbed file in its `<media:content>` instead, `<channel><language>` is set, and the rest of the feed passes through. Items not done yet are left out. Needs an `MrssSource`; one per language."
      },
      "Config.OpenAITranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "OpenAI",
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "GPT-5.4 mini",
              "GPT-5.6 Luna",
              "GPT-5.6 Luna (Thinking)"
            ],
            "default": "GPT-5.6 Luna",
            "title": "Model",
            "description": "The model."
          },
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "extraPrompt": {
            "type": "string",
            "default": "Avoid profane language in translated output.",
            "description": "Instructions for the translator: style, register, audience. They replace the default, so restate it to keep it.",
            "title": "Extra Prompt"
          },
          "latencyMode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "quality",
              "low-latency",
              null
            ],
            "default": null,
            "description": "`quality` translates whole sentences; `low-latency` smaller units. Left out, it is chosen to suit the transcription engine.",
            "title": "Latency Mode",
            "options": {
              "additional": true
            }
          },
          "compression": {
            "type": "boolean",
            "default": true,
            "description": "Condense long translations, which keeps a dub in step with the original speech.",
            "title": "Compression",
            "options": {
              "additional": true
            }
          },
          "enableSpeakerGenderAware": {
            "type": "boolean",
            "default": true,
            "description": "Deprecated by the build; kept so older configs stay valid.",
            "title": "Enable Speaker Gender Aware",
            "deprecated": true
          },
          "@type": {
            "default": "OpenAI",
            "const": "OpenAI",
            "type": "string",
            "enum": [
              "OpenAI"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "OpenAI"
          }
        },
        "description": "OpenAI GPT: natural, context-aware translation."
      },
      "Config.NovaTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Nova",
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "Nova 2 Lite"
            ],
            "default": "Nova 2 Lite",
            "title": "Model",
            "description": "The model."
          },
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "extraPrompt": {
            "type": "string",
            "default": "Avoid profane language in translated output.",
            "description": "Instructions for the translator: style, register, audience. They replace the default, so restate it to keep it.",
            "title": "Extra Prompt"
          },
          "latencyMode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "quality",
              "low-latency",
              null
            ],
            "default": null,
            "description": "`quality` translates whole sentences; `low-latency` smaller units. Left out, it is chosen to suit the transcription engine.",
            "title": "Latency Mode",
            "options": {
              "additional": true
            }
          },
          "compression": {
            "type": "boolean",
            "default": true,
            "description": "Condense long translations, which keeps a dub in step with the original speech.",
            "title": "Compression",
            "options": {
              "additional": true
            }
          },
          "enableSpeakerGenderAware": {
            "type": "boolean",
            "default": true,
            "description": "Deprecated by the build; kept so older configs stay valid.",
            "title": "Enable Speaker Gender Aware",
            "deprecated": true
          },
          "@type": {
            "default": "Nova",
            "const": "Nova",
            "type": "string",
            "enum": [
              "Nova"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Nova"
          }
        },
        "description": "Amazon Nova: natural, context-aware translation."
      },
      "Config.SonioxTranslationProvider": {
        "type": "object",
        "additionalProperties": false,
        "title": "Soniox translation",
        "properties": {
          "glossary": {
            "anyOf": [
              {
                "type": "null",
                "title": "Not Set"
              },
              {
                "title": "Included",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              {
                "type": "integer"
              }
            ],
            "default": null,
            "description": "Terms with a fixed translation, `{\"source term\": \"translation\"}`: names, brands, terminology. Or the id of a glossary of your account, from the transcription language into this one, managed with the Live API (`/v2/glossaries`).",
            "title": "Glossary"
          },
          "@type": {
            "default": "Soniox",
            "const": "Soniox",
            "type": "string",
            "enum": [
              "Soniox"
            ]
          }
        },
        "required": [
          "@type"
        ],
        "options": {
          "multiple_editor_select_via_property": {
            "property": "@type",
            "value": "Soniox"
          }
        },
        "description": "Pairs with Soniox transcription."
      }
    }
  }
}
