{
  "openapi": "3.1.0",
  "info": {
    "title": "VidLab API",
    "version": "2026-11-01",
    "summary": "One key for the top image, video and audio models, routed to the best provider at the lowest price.",
    "description": "Prepaid and pay-per-outcome: each generation holds its catalogue price while it runs, a success is charged once, a failure is never charged. Docs: https://vidlab.ai/vidlab-api/docs. For agents: https://vidlab.ai/vidlab-api/llms.txt.",
    "contact": {
      "email": "support@vidlab.ai"
    }
  },
  "servers": [
    {
      "url": "https://vidlab.ai",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "VidLab API docs",
    "url": "https://vidlab.ai/vidlab-api/docs"
  },
  "tags": [
    {
      "name": "Models",
      "description": "The catalogue, status and free estimates. No key."
    },
    {
      "name": "Generations",
      "description": "Start and poll generations. API key required."
    },
    {
      "name": "Formats",
      "description": "Our ad formats, run end to end with a question loop. Listing is public; runs need an API key."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "paths": {
    "/v1/models": {
      "get": {
        "operationId": "listModels",
        "summary": "List models",
        "tags": [
          "Models"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Kind"
          },
          {
            "$ref": "#/components/parameters/Maker"
          },
          {
            "$ref": "#/components/parameters/Search"
          }
        ],
        "responses": {
          "200": {
            "description": "Every available model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/open/catalog": {
      "get": {
        "operationId": "listModelsOpenCatalog",
        "summary": "List models (alias of /v1/models)",
        "tags": [
          "Models"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Kind"
          },
          {
            "$ref": "#/components/parameters/Maker"
          },
          {
            "$ref": "#/components/parameters/Search"
          }
        ],
        "responses": {
          "200": {
            "description": "Every available model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/models/{id}": {
      "get": {
        "operationId": "getModel",
        "summary": "One model",
        "tags": [
          "Models"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Each model's status",
        "tags": [
          "Models"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "operationId": "getBalance",
        "summary": "The API balance",
        "description": "What this key's account can spend now. Free, read only, any key.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "operationId": "uploadFile",
        "summary": "Upload a file",
        "description": "Send the file itself as the request body (no multipart), with its type in Content-Type and, if you like, its name in X-File-Name. The answer is a link to put in an input field. Pictures (JPEG, PNG, WEBP, GIF) up to 20 MB, videos (MP4, MOV, WEBM) up to 200 MB, sounds (MP3, WAV, M4A, OGG, WEBM) up to 50 MB. The real type is checked against the bytes. Files are kept 7 days. Free.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/gif": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/quicktime": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "video/webm": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "audio/mpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "audio/wav": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "audio/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "audio/ogg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "audio/webm": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-File-Name",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "The file's name, for your own records. It never becomes part of the link."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "file_too_large: over the limit for its kind.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type: not a picture, video or sound we take, or not what its Content-Type says.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/estimate": {
      "post": {
        "operationId": "estimate",
        "summary": "Price one call (free, no key)",
        "tags": [
          "Models"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/open/estimate": {
      "post": {
        "operationId": "estimateOpen",
        "summary": "Price one call (free, no key)",
        "tags": [
          "Models"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/generations": {
      "post": {
        "operationId": "createGeneration",
        "summary": "Start a generation",
        "description": "Checks and prices the input, holds the price on the prepaid API wallet, and queues the job. Always asynchronous: poll GET /v1/generations/{id}. A failed job is never charged.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued (or the existing job for a repeated Idempotency-Key).",
            "headers": {
              "x-wallet-remaining-cents": {
                "description": "Available balance after this job's hold, in cents.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-wallet-resets-at": {
                "description": "When included monthly credit resets.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "5XX": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "get": {
        "operationId": "listGenerations",
        "summary": "List your generations",
        "description": "Your account's generations, newest first, 25 to a page. Free and never rate limited.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ListLimit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/ListStatus"
          },
          {
            "$ref": "#/components/parameters/ListModel"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/generations/quote": {
      "post": {
        "operationId": "quoteGeneration",
        "summary": "Quote a generation (free, makes nothing)",
        "description": "The same body as POST /v1/generations, the same checks and the same price, but nothing is created or held. Answers what it would cost, what is reserved, how long it is expected to take and whether your wallet has the credit. Needs a key (it reads your wallet). /v1/estimate stays the keyless price.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/video-router/generate": {
      "post": {
        "operationId": "createGenerationVideoRouter",
        "summary": "Start a generation (alias)",
        "description": "Checks and prices the input, holds the price on the prepaid API wallet, and queues the job. Always asynchronous: poll GET /v1/generations/{id}. A failed job is never charged.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued (or the existing job for a repeated Idempotency-Key).",
            "headers": {
              "x-wallet-remaining-cents": {
                "description": "Available balance after this job's hold, in cents.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-wallet-resets-at": {
                "description": "When included monthly credit resets.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "5XX": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/image-router/generate": {
      "post": {
        "operationId": "createGenerationImageRouter",
        "summary": "Start a generation (alias)",
        "description": "Checks and prices the input, holds the price on the prepaid API wallet, and queues the job. Always asynchronous: poll GET /v1/generations/{id}. A failed job is never charged.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued (or the existing job for a repeated Idempotency-Key).",
            "headers": {
              "x-wallet-remaining-cents": {
                "description": "Available balance after this job's hold, in cents.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-wallet-resets-at": {
                "description": "When included monthly credit resets.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "5XX": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/generations/{id}": {
      "get": {
        "operationId": "getGeneration",
        "summary": "Poll a generation",
        "description": "Free and never rate limited. Poll every 3 to 10 seconds until succeeded or failed.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/generations/{id}/content": {
      "get": {
        "operationId": "getGenerationContent",
        "summary": "The finished file",
        "description": "A redirect (302) to the finished file in VidLab's storage, signed for this call. Send the key; do not forward it to the file. 409 not_ready until the generation has succeeded.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          }
        ],
        "responses": {
          "302": {
            "description": "The file.",
            "headers": {
              "Location": {
                "description": "Signed link to the file.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/generations/{id}/content/{n}": {
      "get": {
        "operationId": "getGenerationContentN",
        "summary": "The nth finished file",
        "description": "A redirect (302) to the finished file in VidLab's storage, signed for this call. Send the key; do not forward it to the file. 409 not_ready until the generation has succeeded.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          },
          {
            "$ref": "#/components/parameters/OutputNumber"
          }
        ],
        "responses": {
          "302": {
            "description": "The file.",
            "headers": {
              "Location": {
                "description": "Signed link to the file.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Poll a generation (alias)",
        "description": "Free and never rate limited. Poll every 3 to 10 seconds until succeeded or failed.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/{id}/status": {
      "get": {
        "operationId": "getJobStatus",
        "summary": "Poll a generation (alias)",
        "description": "Free and never rate limited. Poll every 3 to 10 seconds until succeeded or failed.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/{id}/result": {
      "get": {
        "operationId": "getJobResult",
        "summary": "Poll a generation (alias)",
        "description": "Free and never rate limited. Poll every 3 to 10 seconds until succeeded or failed.",
        "tags": [
          "Generations"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/GenerationId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/formats": {
      "get": {
        "operationId": "listFormats",
        "summary": "List the formats",
        "tags": [
          "Formats"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/formats/{format_id}": {
      "get": {
        "operationId": "getFormat",
        "summary": "One format: its inputs, questions and price rule",
        "tags": [
          "Formats"
        ],
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Format"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/formats/{format_id}/runs": {
      "post": {
        "operationId": "startFormatRun",
        "summary": "Start a format run",
        "description": "Returns the run at once. needs_input: answer its questions. Nothing is charged before the price is approved (confirm_price, or max_price_usd at or above the quote).",
        "tags": [
          "Formats"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatId"
          },
          {
            "$ref": "#/components/parameters/FormatIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FormatRunRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A replay of a run that is already over.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatRun"
                }
              }
            }
          },
          "202": {
            "description": "The run, while open.",
            "headers": {
              "x-wallet-remaining-cents": {
                "description": "Available balance after this job's hold, in cents.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-wallet-resets-at": {
                "description": "When included monthly credit resets.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "idempotency_conflict, not_waiting_for_input or answer_in_flight.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/v1/formats/runs/{run_id}": {
      "get": {
        "operationId": "getFormatRun",
        "summary": "Read a format run",
        "description": "Free of request limits. Poll every 10 to 20 seconds while queued or running.",
        "tags": [
          "Formats"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatRunId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/formats/runs/{run_id}/answers": {
      "post": {
        "operationId": "answerFormatRun",
        "summary": "Answer a run's open questions",
        "tags": [
          "Formats"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatRunId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FormatAnswers"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The run: the next questions, or queued.",
            "headers": {
              "x-wallet-remaining-cents": {
                "description": "Available balance after this job's hold, in cents.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-wallet-resets-at": {
                "description": "When included monthly credit resets.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "idempotency_conflict, not_waiting_for_input or answer_in_flight.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/formats/runs/{run_id}/cancel": {
      "post": {
        "operationId": "cancelFormatRun",
        "summary": "Stop a run",
        "description": "A run still asking ends with nothing charged; a started run is charged what already ran. Safe to repeat.",
        "tags": [
          "Formats"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FormatRunId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormatRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer vidlab_sk_... (an API key from the dashboard)."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    },
    "parameters": {
      "ModelId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$"
        },
        "example": "seedance-2.5"
      },
      "GenerationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^gen_[A-Za-z0-9_-]{4,80}$"
        }
      },
      "Kind": {
        "name": "kind",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "video",
            "image",
            "audio",
            "lipsync",
            "upscale"
          ]
        }
      },
      "Maker": {
        "name": "maker",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Only this maker's models (any case)."
      },
      "Search": {
        "name": "search",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 200
        },
        "description": "Only models whose id, name, maker or description has every word. q is the same."
      },
      "ListLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "StartingAfter": {
        "name": "starting_after",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^gen_[A-Za-z0-9_-]{4,80}$"
        },
        "description": "The last id of the page before."
      },
      "ListStatus": {
        "name": "status",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "queued",
            "running",
            "succeeded",
            "failed"
          ]
        }
      },
      "ListModel": {
        "name": "model",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Only this model's generations."
      },
      "OutputNumber": {
        "name": "n",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 99
        },
        "description": "Which file, from 1."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_:.-]{8,100}$"
        },
        "description": "A retry with the same key returns the job the first call made: nothing is started, held or charged twice. Tied to your account and to that first job."
      },
      "FormatId": {
        "name": "format_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,59}$"
        },
        "example": "talking-head-ugc"
      },
      "FormatRunId": {
        "name": "run_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^fmtrun_[0-9a-f]{32}$"
        }
      },
      "FormatIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.:-]{1,128}$"
        },
        "description": "For the rest of the UTC day the same key with the same request returns the original run (header idempotent-replayed: true); with a different request it is refused (409 idempotency_conflict)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request: invalid_json, invalid_request, invalid_input, variant_unavailable, max_price_exceeded, invalid_idempotency_key, invalid_limit, invalid_status, invalid_model or invalid_starting_after.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "not_ready (the generation has not finished) or generation_failed (it failed, so there is no file).",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "max_cost_exceeded or max_wait_exceeded: your max_cost_usd or max_wait_s leaves no route that can take the call. Nothing was held.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "unauthorized: the key is missing, unknown, revoked or expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "api_key_required, api_key_scope_forbidden or developer_access_forbidden.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "model_not_found, generation_not_found, output_not_found or not_found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooLarge": {
        "description": "request_too_large: the body is over 2 MB.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooMany": {
        "description": "rate_limited, read_rate_limited, request_per_minute, request_per_minute_user, request_per_day, api_wallet_exhausted or api_key_budget_exhausted.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "model_unavailable or api_wallet_reconciliation_required.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "api_error, internal_error or unavailable. Retry with the same Idempotency-Key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Input": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 20000,
            "description": "What to make."
          },
          "negative_prompt": {
            "type": "string",
            "maxLength": 5000,
            "description": "What to avoid."
          },
          "images": {
            "type": "array",
            "maxItems": 16,
            "items": {
              "type": "string",
              "pattern": "^(https://|data:[A-Za-z]+/[A-Za-z0-9.+-]+;base64,)",
              "maxLength": 1900000
            },
            "description": "Reference images, each a public https URL or a base64 data URL; a first frame or main reference goes first. At most 16, or the model's params.images.max."
          },
          "end_image": {
            "type": "string",
            "pattern": "^(https://|data:[A-Za-z]+/[A-Za-z0-9.+-]+;base64,)",
            "maxLength": 1900000,
            "description": "The last frame (https URL or base64 data URL)."
          },
          "video": {
            "type": "string",
            "pattern": "^(https://|data:[A-Za-z]+/[A-Za-z0-9.+-]+;base64,)",
            "maxLength": 1900000,
            "description": "A reference or source video (https URL or base64 data URL). Selects the +video_input variant where one exists."
          },
          "audio": {
            "type": "string",
            "pattern": "^(https://|data:[A-Za-z]+/[A-Za-z0-9.+-]+;base64,)",
            "maxLength": 1900000,
            "description": "A source sound (https URL or base64 data URL)."
          },
          "duration": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 7200,
            "description": "Seconds of output, within the model's params.duration min and max. For models with duration_must_cover, at least the input file's length in whole seconds."
          },
          "resolution": {
            "type": "string",
            "maxLength": 20,
            "description": "One of the model's params.resolution.values, e.g. 720p or 2k."
          },
          "aspect_ratio": {
            "type": "string",
            "maxLength": 20,
            "description": "One of the model's params.aspect_ratio.values, e.g. 9:16."
          },
          "quality": {
            "type": "string",
            "maxLength": 20,
            "description": "One of the model's params.quality.values."
          },
          "generate_audio": {
            "type": "boolean",
            "description": "Ask for sound where the model makes it. Selects the +audio variant."
          },
          "seed": {
            "type": "integer",
            "description": "Repeatable results where supported."
          },
          "n": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "description": "How many images (the model's params.n.max may be lower)."
          },
          "text": {
            "type": "string",
            "maxLength": 20000,
            "description": "Text to speak (voice models)."
          },
          "voice": {
            "type": "string",
            "maxLength": 200,
            "description": "A voice id or name (voice models)."
          },
          "extra": {
            "type": "object",
            "additionalProperties": true,
            "description": "Model-specific options, at most 8 KB as JSON."
          }
        },
        "description": "Only the fields listed in the model's params are accepted; any other is refused (400), never dropped. Values are matched without regard to case. Omitted resolution, quality, aspect_ratio and duration get the model's first allowed value."
      },
      "EstimateRequest": {
        "type": "object",
        "required": [
          "model"
        ],
        "properties": {
          "model": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "seedance-2.5"
            ]
          },
          "input": {
            "$ref": "#/components/schemas/Input"
          }
        },
        "description": "The flat form (input fields at the top level of the body) is read as `input` too."
      },
      "GenerationRequest": {
        "type": "object",
        "required": [
          "model",
          "input"
        ],
        "properties": {
          "model": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "seedance-2.5"
            ]
          },
          "input": {
            "$ref": "#/components/schemas/Input"
          },
          "max_price_usd": {
            "type": "number",
            "minimum": 0,
            "deprecated": true,
            "description": "The older name of the cap, still accepted: a price above it answers 400 max_price_exceeded and nothing is held. Prefer max_cost_usd."
          },
          "max_cost_usd": {
            "type": "number",
            "minimum": 0,
            "description": "The most you will pay for the call. A route whose price for the call is above it is skipped; when none is left the answer is 422 max_cost_exceeded and nothing is held. Checked again on the measured reference-video price. Quote the exact request first (POST /v1/generations/quote) and pass the quote."
          },
          "max_wait_s": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 86400,
            "description": "A route is used only if it is expected to finish within this many seconds, and no new route is tried once this long has passed. When no route is left: 422 max_wait_exceeded."
          },
          "route": {
            "type": "string",
            "enum": [
              "auto",
              "route-1",
              "route-2",
              "route-3"
            ],
            "default": "auto",
            "description": "auto: the cheapest healthy provider with failover. route-N: start from that place in the ranking."
          }
        }
      },
      "ParamSpec": {
        "type": "object",
        "properties": {
          "required": {
            "type": "boolean"
          },
          "values": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            }
          },
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          }
        }
      },
      "Model": {
        "type": "object",
        "required": [
          "id",
          "name",
          "maker",
          "kind",
          "unit",
          "prices",
          "params",
          "featured",
          "routes",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "seedance-2.5"
            ]
          },
          "name": {
            "type": "string"
          },
          "maker": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "video",
              "image",
              "audio",
              "lipsync",
              "upscale"
            ]
          },
          "unit": {
            "type": "string",
            "enum": [
              "per_second",
              "per_image",
              "per_job",
              "per_1k_chars",
              "per_minute"
            ],
            "description": "per_second (from duration), per_image (from n), per_job (1), per_1k_chars (each started 1,000 characters of text), per_minute (each started minute)."
          },
          "description": {
            "type": "string"
          },
          "prices": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "USD per unit by variant key: the resolution or quality, then +audio and +video_input flags; * for a single price."
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ParamSpec"
            },
            "description": "The input fields this model takes."
          },
          "media": {
            "type": "object",
            "properties": {
              "still": {
                "type": "string"
              },
              "video": {
                "type": "string"
              }
            }
          },
          "featured": {
            "type": "boolean"
          },
          "save_pct": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99,
            "description": "How much cheaper we are, percent, at the setting the price starts from (our cheapest one), rounded down."
          },
          "offer": {
            "type": "object",
            "description": "The price a model card leads with: our cheapest plain option against the usual price for the same unit.",
            "properties": {
              "variant": {
                "type": "string"
              },
              "price_usd": {
                "type": "number"
              },
              "market_usd": {
                "type": "number"
              },
              "pct": {
                "type": "integer",
                "minimum": 1,
                "maximum": 99
              },
              "checked": {
                "type": "string"
              }
            }
          },
          "duration_must_cover": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "video",
                "audio"
              ]
            },
            "description": "duration must be at least the length of these input files."
          },
          "routes": {
            "type": "integer",
            "minimum": 0,
            "description": "How many providers can run it now."
          },
          "status": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "down"
            ]
          }
        }
      },
      "ModelList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "ModelResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Model"
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "object",
              "available_cents",
              "available_usd"
            ],
            "properties": {
              "object": {
                "const": "balance"
              },
              "available_cents": {
                "type": "integer",
                "description": "What the key can spend now: balance plus monthly credit left, minus holds."
              },
              "available_usd": {
                "type": "number"
              },
              "balance_cents": {
                "type": "integer"
              },
              "monthly_credit_remaining_cents": {
                "type": "integer"
              },
              "held_cents": {
                "type": "integer",
                "description": "Reserved by generations still running."
              },
              "resets_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      },
      "UploadResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "url",
              "mime",
              "bytes",
              "kind",
              "expires_at"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "A link to the stored file. Put it in images, end_image, video or audio. It works for about a week."
              },
              "mime": {
                "type": "string",
                "description": "The file's real type, read from its first bytes.",
                "example": "image/png"
              },
              "bytes": {
                "type": "integer",
                "description": "Size of the stored file."
              },
              "kind": {
                "type": "string",
                "enum": [
                  "image",
                  "video",
                  "audio"
                ]
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "description": "When the link stops working. The file itself is deleted 7 days after the upload."
              }
            }
          }
        }
      },
      "StatusList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "model",
                "status",
                "routes"
              ],
              "properties": {
                "model": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "operational",
                    "degraded",
                    "down"
                  ]
                },
                "routes": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          }
        }
      },
      "Estimate": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "model",
              "variant",
              "unit",
              "units",
              "price_usd"
            ],
            "properties": {
              "model": {
                "type": "string"
              },
              "variant": {
                "type": "string"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "per_second",
                  "per_image",
                  "per_job",
                  "per_1k_chars",
                  "per_minute"
                ]
              },
              "units": {
                "type": "number"
              },
              "price_usd": {
                "type": "number",
                "description": "The exact price a generation with this input holds and, on success, charges."
              },
              "duration_must_cover": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "video",
                    "audio"
                  ]
                }
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "object",
              "model",
              "variant",
              "unit",
              "units",
              "price_usd",
              "quoted_usd",
              "reserved_usd",
              "estimated_seconds",
              "available_usd",
              "enough_credit"
            ],
            "properties": {
              "object": {
                "const": "quote"
              },
              "model": {
                "type": "string"
              },
              "variant": {
                "type": "string"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "per_second",
                  "per_image",
                  "per_job",
                  "per_1k_chars",
                  "per_minute"
                ]
              },
              "units": {
                "type": "number",
                "description": "For a reference-video price, the output seconds plus the reference video's seconds (declared with extra.video_seconds, else as long as the output)."
              },
              "price_usd": {
                "type": "number",
                "description": "Same as quoted_usd."
              },
              "quoted_usd": {
                "type": "number",
                "description": "What the generation costs if it succeeds. The estimate's price_usd."
              },
              "reserved_usd": {
                "type": "number",
                "description": "What is held on the wallet while it runs."
              },
              "estimated_seconds": {
                "type": "number",
                "description": "How long it is expected to take, from recent finished jobs of this model."
              },
              "available_usd": {
                "type": "number",
                "description": "What the wallet can spend now."
              },
              "enough_credit": {
                "type": "boolean",
                "description": "Whether available_usd covers reserved_usd. If false, POST /v1/generations answers 429 api_wallet_exhausted."
              },
              "duration_must_cover": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "video",
                    "audio"
                  ]
                }
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "GenerationList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Generation"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Send starting_after with the last id for the next page."
          }
        }
      },
      "Output": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed link, valid 6 hours; each poll returns a working link."
          },
          "mime": {
            "type": "string"
          }
        }
      },
      "Generation": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "model",
          "variant",
          "units",
          "price_usd",
          "outputs",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^gen_[0-9A-Za-z]{24}$"
          },
          "object": {
            "const": "generation"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed"
            ],
            "description": "succeeded and failed are final. A failed job is never charged."
          },
          "model": {
            "type": "string"
          },
          "variant": {
            "type": "string"
          },
          "units": {
            "type": "number"
          },
          "price_usd": {
            "type": "number",
            "description": "Held while the job runs, charged once on success, released on failure."
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Output"
            }
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "retryable"
            ],
            "description": "Only on a failed generation. A failed generation is never charged.",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "content_policy",
                  "invalid_input",
                  "model_declined",
                  "reference_changed",
                  "max_cost_exceeded",
                  "max_wait_exceeded",
                  "model_busy",
                  "model_unavailable",
                  "all_routes_failed",
                  "timeout",
                  "processing_failed"
                ],
                "description": "Stable: branch on it."
              },
              "message": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean",
                "description": "True when the same request may work if sent again (a provider's trouble, not your input)."
              },
              "reason": {
                "type": "string",
                "enum": [
                  "sexual",
                  "minors",
                  "copyright",
                  "real_person",
                  "other"
                ],
                "description": "Only on content_policy: the kind of block."
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          },
          "status_url": {
            "type": "string",
            "description": "Create answer only."
          },
          "result_url": {
            "type": "string",
            "description": "Create answer only."
          }
        }
      },
      "GenerationResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Generation"
          }
        }
      },
      "FormatInput": {
        "type": "object",
        "required": [
          "name",
          "type",
          "required"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "choice",
              "image",
              "video",
              "url"
            ],
            "description": "image and video take a public https URL (30 MB and 300 MB at most)."
          },
          "required": {
            "type": "boolean"
          },
          "description": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "default": {
            "type": "string"
          },
          "max_length": {
            "type": "integer"
          }
        }
      },
      "FormatQuestion": {
        "type": "object",
        "required": [
          "id",
          "text",
          "type",
          "required"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Answer under this id. For an input question it is the input's name; the price question is confirm_price."
          },
          "text": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "choice",
              "file"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "required": {
            "type": "boolean"
          }
        }
      },
      "Format": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "inputs",
          "price"
        ],
        "properties": {
          "object": {
            "const": "format"
          },
          "id": {
            "type": "string",
            "examples": [
              "talking-head-ugc"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "About how long a run takes."
          },
          "inputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FormatInput"
            }
          },
          "example": {
            "type": [
              "object",
              "null"
            ]
          },
          "price": {
            "type": "object",
            "properties": {
              "rule": {
                "const": "quoted_per_run"
              },
              "from_usd": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The price of the default inputs."
              },
              "note": {
                "type": "string"
              }
            }
          },
          "questions_it_may_ask": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FormatQuestion"
            }
          }
        }
      },
      "FormatList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Format"
            }
          }
        }
      },
      "FormatRunRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "inputs": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "description": "Input name to value, as the format lists them. Files are public URLs."
          },
          "max_price_usd": {
            "type": "number",
            "minimum": 0,
            "maximum": 10000,
            "description": "Approve in advance: a quote at or below it starts without the price question."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "maxLength": 2000,
            "description": "Unsigned, best effort: one POST per event, 5 s timeout, no retry."
          }
        }
      },
      "FormatAnswers": {
        "type": "object",
        "required": [
          "answers"
        ],
        "additionalProperties": false,
        "properties": {
          "answers": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "description": "Question id to value. confirm_price takes approve or cancel."
          }
        }
      },
      "FormatRun": {
        "type": "object",
        "required": [
          "object",
          "run_id",
          "format",
          "status",
          "questions",
          "inputs",
          "price",
          "created_at"
        ],
        "properties": {
          "object": {
            "const": "format_run"
          },
          "run_id": {
            "type": "string",
            "pattern": "^fmtrun_[0-9a-f]{32}$"
          },
          "format": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "needs_input",
              "queued",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FormatQuestion"
            }
          },
          "inputs": {
            "type": "object"
          },
          "price": {
            "type": "object",
            "properties": {
              "quoted_usd": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "max_price_usd": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "charged_usd": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Set once the run is over: what it actually cost, never more than the approved quote."
              }
            }
          },
          "progress": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "stage": {
                "type": "string"
              },
              "done": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              }
            }
          },
          "output": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "videos": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "mime_type": {
                      "type": "string"
                    },
                    "duration_seconds": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string"
                    }
                  }
                }
              },
              "duration_seconds": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "next_action": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_url": {
            "type": "string"
          },
          "answers_url": {
            "type": "string"
          },
          "cancel_url": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": true,
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type",
              "code"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "rate_limit_error",
                  "api_error",
                  "service_unavailable_error"
                ]
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_json",
                  "invalid_request",
                  "invalid_input",
                  "variant_unavailable",
                  "max_price_exceeded",
                  "invalid_idempotency_key",
                  "invalid_limit",
                  "invalid_status",
                  "invalid_model",
                  "invalid_starting_after",
                  "empty_file",
                  "upload_interrupted",
                  "unauthorized",
                  "key_expired",
                  "key_quota_exceeded",
                  "ip_not_allowed",
                  "model_not_allowed",
                  "api_key_required",
                  "api_key_scope_forbidden",
                  "developer_access_forbidden",
                  "model_not_found",
                  "generation_not_found",
                  "output_not_found",
                  "not_found",
                  "method_not_allowed",
                  "not_ready",
                  "generation_failed",
                  "request_too_large",
                  "file_too_large",
                  "unsupported_media_type",
                  "max_cost_exceeded",
                  "max_wait_exceeded",
                  "rate_limited",
                  "daily_upload_limit",
                  "too_many_uploads",
                  "request_per_minute",
                  "request_per_minute_user",
                  "request_per_day",
                  "read_rate_limited",
                  "api_wallet_exhausted",
                  "api_key_budget_exhausted",
                  "api_wallet_reconciliation_required",
                  "model_unavailable",
                  "api_error",
                  "internal_error",
                  "unavailable"
                ],
                "description": "Stable: branch on it. The HTTP status of each code is in x-error-statuses."
              }
            }
          }
        },
        "x-error-statuses": {
          "invalid_json": 400,
          "invalid_request": 400,
          "invalid_input": 400,
          "variant_unavailable": 400,
          "max_price_exceeded": 400,
          "invalid_idempotency_key": 400,
          "invalid_limit": 400,
          "invalid_status": 400,
          "invalid_model": 400,
          "invalid_starting_after": 400,
          "empty_file": 400,
          "upload_interrupted": 400,
          "unauthorized": 401,
          "key_expired": 401,
          "key_quota_exceeded": 402,
          "ip_not_allowed": 403,
          "model_not_allowed": 403,
          "api_key_required": 403,
          "api_key_scope_forbidden": 403,
          "developer_access_forbidden": 403,
          "model_not_found": 404,
          "generation_not_found": 404,
          "output_not_found": 404,
          "not_found": 404,
          "method_not_allowed": 405,
          "not_ready": 409,
          "generation_failed": 409,
          "request_too_large": 413,
          "file_too_large": 413,
          "unsupported_media_type": 415,
          "max_cost_exceeded": 422,
          "max_wait_exceeded": 422,
          "rate_limited": 429,
          "daily_upload_limit": 429,
          "too_many_uploads": 429,
          "request_per_minute": 429,
          "request_per_minute_user": 429,
          "request_per_day": 429,
          "read_rate_limited": 429,
          "api_wallet_exhausted": 429,
          "api_key_budget_exhausted": 429,
          "api_wallet_reconciliation_required": 503,
          "model_unavailable": 503,
          "api_error": 500,
          "internal_error": 500,
          "unavailable": 502
        },
        "description": "Some errors add fields beside error: price_usd, wallet, key_budget, limits, observed, plan, reset_at, request_id."
      }
    }
  }
}