{
  "openapi": "3.1.0",
  "info": {
    "title": "FotoAI API",
    "version": "1.0.0",
    "summary": "Generate AI photos, videos and Hungarian speech with the FotoAI model catalog.",
    "description": "FotoAI (fotoai.hu) is a Hungarian AI photo, video and voice studio. This API exposes the same\nmodels, prices and gates as the web app: list models and styles, get a price quote, start a\ngeneration, poll it (or receive a signed webhook) and download the outputs.\n\n**Authentication.** Every `/api/v1/*` call needs `Authorization: Bearer <token>`:\n- a personal API key (`fai_live_…`), self-serve at https://fotoai.hu/beallitasok/api-kulcsok (Pro plan), or\n- an OAuth 2.1 access token (`fai_oat_…`, authorization code + PKCE S256) for partner apps —\n  any plan, the free signup credits included. Metadata: `/.well-known/oauth-authorization-server`.\n\n`GET /api/catalog` and `GET /api/health` are public (no token).\n\n**Money** is integer millicredits (1 credit = 1000 mc). **Times** are ISO 8601 UTC.\n\n**Errors** are always JSON: `{ \"error\": { \"code\", \"message\", \"details\" } }` — `code` is the\nstable contract, `message` is Hungarian and for humans, `details` carries the fields needed to\nresolve it (e.g. `topup_url`, `plans_url`, `requiredTier`, `Retry-After` on 429).\n\nFull guide: https://fotoai.hu/dokumentacio/api · MCP server: `POST /api/mcp` · CLI: `npx fotoai-cli`.",
    "contact": {
      "name": "FotoAI support",
      "email": "hello@fotoai.hu",
      "url": "https://fotoai.hu/dokumentacio"
    },
    "termsOfService": "https://fotoai.hu/tos"
  },
  "externalDocs": {
    "description": "FotoAI developer documentation (Hungarian)",
    "url": "https://fotoai.hu/dokumentacio/api"
  },
  "servers": [
    {
      "url": "https://fotoai.hu",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Public",
      "description": "No authentication."
    },
    {
      "name": "Account",
      "description": "The caller's account and balance."
    },
    {
      "name": "Catalog",
      "description": "Models and styles the caller can use."
    },
    {
      "name": "Generations",
      "description": "Quote, start, poll and cancel generations."
    },
    {
      "name": "Uploads",
      "description": "Reference images, start/end frames, reference video."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Public"
        ],
        "summary": "Service health",
        "description": "Liveness probe. Answers `{ ok: true }` when the service is up. No authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog": {
      "get": {
        "operationId": "getPublicCatalog",
        "tags": [
          "Public"
        ],
        "summary": "Public model catalog",
        "description": "Every enabled model (SKU) the web app sells, with its category, vendor, display name, modes and parameters. Public and cacheable (s-maxage=300). `include=prices` adds the per-config prices in millicredits, `plans` the plan tiers and credit packs, `hub` the grouped navigation. Provider costs and margins are never included.",
        "security": [],
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma separated: `prices`, `plans`, `hub`.",
            "schema": {
              "type": "string",
              "examples": [
                "prices,plans"
              ]
            }
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "description": "Price the catalog for this plan tier.",
            "schema": {
              "type": "string",
              "enum": [
                "free",
                "alap",
                "plusz",
                "pro"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicCatalog"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Current account and balance",
        "description": "The authenticated account. `email` and `name` need the `profile` scope; the balance fields need `read`. A personal API key counts as both.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "profile",
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/models": {
      "get": {
        "operationId": "listModels",
        "tags": [
          "Catalog"
        ],
        "summary": "List models",
        "description": "Enabled models with modes, input slots, parameters, limits and prices — generated from the same catalog the web app sells, so it cannot drift. `can_use` says whether the caller's plan may use the model.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filter by category.",
            "schema": {
              "type": "string",
              "enum": [
                "image",
                "video",
                "audio",
                "tool"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The models.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/styles": {
      "get": {
        "operationId": "listStyles",
        "tags": [
          "Catalog"
        ],
        "summary": "List styles and photo packs",
        "description": "Curated styles (`pack: false`, add a prompt prefix/suffix and defaults) and photo packs (`pack: true`, a generation becomes a pack run of `pack_size` images, priced per image).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The styles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StyleList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/quote": {
      "post": {
        "operationId": "createQuote",
        "tags": [
          "Generations"
        ],
        "summary": "Price a generation",
        "description": "Prices a request exactly as `createGeneration` would, without spending anything. The quote is stored for 10 minutes; pass its `quote_id` to `createGeneration` to cap the price. `model` or `style_id` is required.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The price.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/api/v1/generations": {
      "post": {
        "operationId": "createGeneration",
        "tags": [
          "Generations"
        ],
        "summary": "Start a generation",
        "description": "Starts a job (or a pack run for a pack style). The server re-quotes; the charged price must be ≤ `max_millicredits` / the stored quote, else `409 PRICE_CHANGED` and nothing is held. `idempotency_key` is required: a repeat returns the same job with `200` and no second hold. Spends credits.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "generate"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent repeat: the existing job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationStarted"
                }
              }
            }
          },
          "202": {
            "description": "Accepted and running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationStarted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      },
      "get": {
        "operationId": "listGenerations",
        "tags": [
          "Generations"
        ],
        "summary": "List recent generations",
        "description": "Newest first, from every channel (web, partner apps, MCP, CLI). Pass `next_cursor` back as `cursor`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size (max 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of generations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/generations/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "A job id, or a pack run's `group_id`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getGeneration",
        "tags": [
          "Generations"
        ],
        "summary": "Get a generation",
        "description": "Status and outputs. Output `url`s are fetchable without credentials (public media URL, or a presigned GET valid 6 hours with `url_expires_at`). A running job whose provider already finished is finalized synchronously.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The generation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      },
      "delete": {
        "operationId": "cancelGeneration",
        "tags": [
          "Generations"
        ],
        "summary": "Cancel a generation",
        "description": "Cancels a job that has not started rendering and releases its hold. A job the provider already runs answers `cancelled: false, reason: \"running\"`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "generate"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The generation after the cancel attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/uploads": {
      "post": {
        "operationId": "createUpload",
        "tags": [
          "Uploads"
        ],
        "summary": "Start an upload",
        "description": "Returns a presigned `put_url` (15 minutes). PUT the bytes with exactly the returned `headers`, then call `completeUpload`. The server never fetches URLs on your behalf.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "generate"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Presigned upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    },
    "/api/v1/uploads/{id}/complete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The `upload_id` from `createUpload`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "completeUpload",
        "tags": [
          "Uploads"
        ],
        "summary": "Finish an upload",
        "description": "Verifies the uploaded object (owner, real size, magic bytes) and returns the `asset_id` to use in `inputs`. A repeated call returns the same asset.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "generate"
            ]
          }
        ],
        "responses": {
          "201": {
            "description": "The asset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset_id": {
                      "type": "string",
                      "pattern": "^[a-f0-9]{24}$",
                      "description": "An asset id from `POST /api/v1/uploads/{id}/complete` or a previous output."
                    }
                  },
                  "required": [
                    "asset_id"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A personal API key `fai_live_…` (Pro plan) or an OAuth access token `fai_oat_…`."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE (S256, required). Confidential partner clients are registered by FotoAI; no dynamic registration.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://fotoai.hu/oauth/authorize",
            "tokenUrl": "https://fotoai.hu/oauth/token",
            "refreshUrl": "https://fotoai.hu/oauth/token",
            "scopes": {
              "profile": "E-mail and display name (GET /api/v1/me).",
              "read": "Balance, models, styles, quotes, generations and outputs.",
              "generate": "Create and cancel generations and uploads — spends credits."
            }
          }
        }
      }
    },
    "responses": {
      "E400": {
        "description": "VALIDATION — `details.issues` lists the invalid fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E401": {
        "description": "UNAUTHENTICATED — missing, unknown, expired or revoked token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E402": {
        "description": "PAYWALL (plan gate: `details.requiredTier`, `plans_url`) or INSUFFICIENT_CREDITS (`details.required`, `available`, `topup_url`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E403": {
        "description": "FORBIDDEN — missing scope (`details.scope`), `session_only` route, debt or policy block.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E404": {
        "description": "NOT_FOUND — unknown id, or another user's.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E409": {
        "description": "PRICE_CHANGED — the fresh price is above the ceiling: `details.quote`, `details.max_millicredits`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E429": {
        "description": "RATE_LIMITED, CONCURRENCY_LIMIT or DAILY_CAP — wait `Retry-After` seconds and retry.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E503": {
        "description": "SERVICE_PAUSED, MODEL_UNAVAILABLE or PROVIDER_ERROR — nothing was charged; retry later or pick another model.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable, machine-readable error code.",
                "enum": [
                  "VALIDATION",
                  "UNAUTHENTICATED",
                  "PAYWALL",
                  "INSUFFICIENT_CREDITS",
                  "FORBIDDEN",
                  "NOT_FOUND",
                  "PRICE_CHANGED",
                  "RATE_LIMITED",
                  "CONCURRENCY_LIMIT",
                  "DAILY_CAP",
                  "SERVICE_PAUSED",
                  "MODEL_UNAVAILABLE",
                  "PROVIDER_ERROR",
                  "BUDGET"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable, in Hungarian."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Fields to resolve the error: `issues`, `topup_url`, `plans_url`, `requiredTier`, `scope`, `resetAt`, `hint`, `docs_url`…"
              }
            }
          }
        },
        "examples": [
          {
            "error": {
              "code": "INSUFFICIENT_CREDITS",
              "message": "Nincs elég kredited ehhez a generáláshoz.",
              "details": {
                "required": 1000,
                "available": 0,
                "shortfall": 1000,
                "topup_url": "https://fotoai.hu/arak#csomagok"
              }
            }
          }
        ]
      },
      "Me": {
        "type": "object",
        "required": [
          "id",
          "tier"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "alap",
              "plusz",
              "pro"
            ]
          },
          "balance_credits": {
            "type": "number"
          },
          "balance_millicredits": {
            "type": "integer"
          },
          "debt_millicredits": {
            "type": "integer"
          },
          "topup_url": {
            "type": "string",
            "format": "uri"
          },
          "plans_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ModelInputSlot": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio"
            ]
          },
          "min": {
            "type": "integer"
          },
          "max": {
            "type": "integer"
          },
          "required": {
            "type": "boolean"
          }
        }
      },
      "Model": {
        "type": "object",
        "required": [
          "id",
          "name",
          "category",
          "modes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "image.seedream-5-lite"
            ]
          },
          "name": {
            "type": "string"
          },
          "vendor": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio",
              "tool"
            ]
          },
          "badge": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier_required": {
            "type": "string"
          },
          "can_use": {
            "type": "boolean"
          },
          "available": {
            "type": "boolean"
          },
          "estimated_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "modes": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "inputs": {
                  "type": "object",
                  "additionalProperties": {
                    "$ref": "#/components/schemas/ModelInputSlot"
                  }
                }
              }
            }
          },
          "params": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "default_params": {
            "type": "object",
            "additionalProperties": true
          },
          "max_duration_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "limits": {
            "type": "object",
            "properties": {
              "max_prompt_chars": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "max_script_chars": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "price": {
            "type": "object",
            "additionalProperties": true,
            "description": "Price configs in millicredits; never provider costs."
          },
          "catalog_version": {
            "type": "string"
          }
        }
      },
      "ModelList": {
        "type": "object",
        "required": [
          "models"
        ],
        "properties": {
          "catalog_version": {
            "type": "string"
          },
          "catalog_hash": {
            "type": "string"
          },
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "Style": {
        "type": "object",
        "required": [
          "id",
          "name",
          "pack"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "category_label": {
            "type": "string"
          },
          "cover_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "requires_reference": {
            "type": "boolean"
          },
          "pack": {
            "type": "boolean"
          },
          "pack_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "default_model": {
            "type": "string"
          },
          "default_params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "StyleList": {
        "type": "object",
        "required": [
          "styles"
        ],
        "properties": {
          "styles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Style"
            }
          },
          "pack_run_max": {
            "type": "integer"
          }
        }
      },
      "Inputs": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "reference_asset_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[a-f0-9]{24}$",
              "description": "An asset id from `POST /api/v1/uploads/{id}/complete` or a previous output."
            },
            "maxItems": 16
          },
          "start_asset_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{24}$",
            "description": "An asset id from `POST /api/v1/uploads/{id}/complete` or a previous output."
          },
          "end_asset_id": {
            "type": "string",
            "pattern": "^[a-f0-9]{24}$",
            "description": "An asset id from `POST /api/v1/uploads/{id}/complete` or a previous output."
          },
          "reference_video_asset_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[a-f0-9]{24}$",
              "description": "An asset id from `POST /api/v1/uploads/{id}/complete` or a previous output."
            },
            "maxItems": 4
          },
          "script": {
            "type": "string",
            "maxLength": 20000,
            "description": "Text to speak (audio models). Defaults to `prompt`."
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "`model` or `style_id` is required.",
        "properties": {
          "model": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "A model id from listModels."
          },
          "mode": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "description": "A key of the model's `modes` (t2i, i2i, t2v, i2v, …)."
          },
          "params": {
            "type": "object",
            "additionalProperties": true,
            "description": "Model parameters (see the model's `params`)."
          },
          "prompt": {
            "type": "string",
            "maxLength": 8000
          },
          "inputs": {
            "$ref": "#/components/schemas/Inputs"
          },
          "style_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "A style or pack id from listStyles."
          }
        }
      },
      "GenerationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key"
        ],
        "description": "`model` or `style_id` is required, and `max_millicredits` or `quote_id`.",
        "properties": {
          "model": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "A model id from listModels."
          },
          "mode": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "description": "A key of the model's `modes`."
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          },
          "prompt": {
            "type": "string",
            "maxLength": 8000
          },
          "inputs": {
            "$ref": "#/components/schemas/Inputs"
          },
          "style_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "max_millicredits": {
            "type": "integer",
            "minimum": 0,
            "description": "Price ceiling."
          },
          "quote_id": {
            "type": "string",
            "format": "uuid",
            "description": "A quote from createQuote (10 min)."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "client_reference": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "millicredits",
          "credits",
          "quote_id"
        ],
        "properties": {
          "millicredits": {
            "type": "integer"
          },
          "credits": {
            "type": "number"
          },
          "breakdown": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "millicredits": {
                  "type": "integer"
                }
              }
            }
          },
          "quote_id": {
            "type": "string",
            "format": "uuid"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "catalog_hash": {
            "type": "string"
          },
          "estimated": {
            "type": "boolean"
          },
          "insufficient": {
            "type": "boolean"
          },
          "available_millicredits": {
            "type": "integer"
          },
          "gated": {
            "type": "object",
            "properties": {
              "requiredTier": {
                "type": "string"
              },
              "plans_url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "topup_url": {
            "type": "string",
            "format": "uri"
          },
          "jobs": {
            "type": "integer",
            "description": "Pack runs only."
          },
          "per_job_millicredits": {
            "type": "integer",
            "description": "Pack runs only."
          }
        }
      },
      "GenerationStarted": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "group_id": {
            "type": "string",
            "description": "Pack runs only."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running"
            ]
          },
          "child_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "estimated_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reserved_millicredits": {
            "type": "integer"
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "code": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Output": {
        "type": "object",
        "required": [
          "kind",
          "url"
        ],
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "url_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "poster_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "mime": {
            "type": "string"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "bytes": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Generation": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "failed",
              "rejected",
              "cancelled"
            ]
          },
          "model": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "estimated_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reserved_millicredits": {
            "type": "integer"
          },
          "charged_millicredits": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Settled amount when completed; 0 when failed/rejected/cancelled; null while running."
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Output"
            }
          },
          "children": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Pack runs only."
          }
        }
      },
      "CancelResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Generation"
          },
          {
            "type": "object",
            "properties": {
              "cancelled": {
                "type": "boolean"
              },
              "reason": {
                "type": "string"
              }
            }
          }
        ]
      },
      "GenerationPage": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Generation"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "UploadRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "kind",
          "mime",
          "bytes"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio"
            ]
          },
          "mime": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "bytes": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "Upload": {
        "type": "object",
        "required": [
          "upload_id",
          "put_url"
        ],
        "properties": {
          "upload_id": {
            "type": "string"
          },
          "put_url": {
            "type": "string",
            "format": "uri"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicCatalog": {
        "type": "object",
        "required": [
          "version",
          "hash",
          "skus"
        ],
        "properties": {
          "version": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "skus": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "category": {
                  "type": "string"
                },
                "vendor": {
                  "type": "string"
                }
              }
            }
          },
          "prices": {
            "type": "object",
            "additionalProperties": true
          },
          "tiers": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "packs": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "hub": {
            "type": "object",
            "additionalProperties": true
          },
          "unavailable": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    }
  }
}