{
  "openapi": "3.0.0",
  "info": {
    "title": "Ojin REST API",
    "version": "v1.0.0",
    "description": "Public REST API surface for the Ojin platform, used for management of model configs (avatar, faces, voices, etc.) and assets (images, videos, etc.). The real-time Model API is documented separately.",
    "contact": {
      "name": "Ojin Platform Team",
      "email": "hello@ojin.ai"
    }
  },
  "servers": [
    {
      "url": "https://api.ojin.ai/v1",
      "description": "Main backend server, version 1."
    }
  ],
  "security": [
    {
      "APIKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Model Variants",
      "description": "Operations related to specific variants of models."
    },
    {
      "name": "Model Configurations",
      "description": "Operations related to user-defined configurations of model variants for API clients and product integrations."
    },
    {
      "name": "Assets",
      "description": "Operations related to managing digital assets (images, videos, etc.)."
    },
    {
      "name": "Public API",
      "description": "Public endpoints for unauthenticated browser clients such as the Ojin widget. These routes use permissive CORS (origin: '*')\nto allow the widget to be embedded on any third-party domain. No authentication is required."
    }
  ],
  "paths": {
    "/model-variants": {
      "get": {
        "tags": [
          "Model Variants"
        ],
        "summary": "List all Model Variants",
        "description": "List all Model Variants.\nIf authenticated, returns variants based on filters (defaulting to all if no filters provided).\nIf unauthenticated, ONLY returns variants with status=\"public\", ignoring other status filters.",
        "operationId": "listModelVariants",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelIdQueryParameter"
          },
          {
            "$ref": "#/components/parameters/StatusQueryParameter"
          },
          {
            "$ref": "#/components/parameters/TagsQueryParameter"
          },
          {
            "$ref": "#/components/parameters/LimitQueryParameter"
          },
          {
            "$ref": "#/components/parameters/OffsetQueryParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of model variants.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ModelVariant"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          }
        }
      }
    },
    "/model-variants/{model_variant_id}": {
      "get": {
        "tags": [
          "Model Variants"
        ],
        "summary": "Retrieve a specific Model Variant",
        "description": "Retrieve a specific Model Variant by ID.\nIf authenticated, returns the variant regardless of status.\nIf unauthenticated, ONLY returns the variant if status=\"public\". Otherwise returns 404.",
        "operationId": "getModelVariantById",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelVariantIdPathParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Model Variant details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelVariant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/model-configs": {
      "get": {
        "tags": [
          "Model Configurations"
        ],
        "summary": "List Model Configurations",
        "description": "Retrieves a list of Model Configurations. By default returns only organization-owned configurations. Use source='template' to retrieve template configurations instead. Supports pagination and filtering by model variant.",
        "operationId": "listModelConfigs",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelVariantIdQueryParameter"
          },
          {
            "$ref": "#/components/parameters/SourceQueryParameter"
          },
          {
            "$ref": "#/components/parameters/LimitQueryParameter"
          },
          {
            "$ref": "#/components/parameters/OffsetQueryParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of model configurations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ModelConfig"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          }
        }
      },
      "post": {
        "tags": [
          "Model Configurations"
        ],
        "summary": "Create a new Model Configuration",
        "operationId": "createModelConfig",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelConfigCreationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Model Configuration created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelConfig"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "description": "Referenced ModelVariant not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/model-configs/{model_config_id}": {
      "get": {
        "tags": [
          "Model Configurations"
        ],
        "summary": "Retrieve a specific Model Configuration",
        "operationId": "getModelConfigById",
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelConfigIdPathParameter"
          }
        ],
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Model Configuration details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      },
      "put": {
        "tags": [
          "Model Configurations"
        ],
        "summary": "Update an existing Model Configuration",
        "operationId": "updateModelConfig",
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelConfigIdPathParameter"
          }
        ],
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelConfigUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Model Configuration updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelConfig"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      },
      "delete": {
        "tags": [
          "Model Configurations"
        ],
        "summary": "Delete a Model Configuration",
        "operationId": "deleteModelConfig",
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelConfigIdPathParameter"
          },
          {
            "$ref": "#/components/parameters/ReferencedByQueryParameter"
          }
        ],
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContentSuccess"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "409": {
            "description": "Conflict - Cannot delete, referenced by AgentConfig.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/assets/initiate-upload": {
      "post": {
        "tags": [
          "Assets"
        ],
        "summary": "Initiate a Multipart Asset Upload",
        "description": "Starts the multipart asset upload process by creating a multipart upload session in S3.\nThe Core API generates a unique `asset_id` and receives an `upload_id` from S3.\nBoth IDs must be used in subsequent part signing and finalization requests.\nNo Asset record is created in the database at this stage.",
        "operationId": "initiateAssetUpload",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Initial metadata for the asset to be uploaded.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssetInitiateUploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Multipart upload initiated successfully. Returns asset_id and upload_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetInitiateUploadResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          }
        }
      }
    },
    "/assets/sign-part": {
      "post": {
        "tags": [
          "Assets"
        ],
        "summary": "Get Pre-signed URL for Upload Part",
        "description": "Generates a pre-signed URL for uploading a specific part of a multipart upload.\nThis endpoint will be called multiple times, once for each part of the file.\nParts must be numbered sequentially starting from 1, and each part (except the last)\nmust be at least 5MB in size (S3 requirement).",
        "operationId": "signAssetUploadPart",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Details for the part to be signed.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssetSignPartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pre-signed URL generated successfully for the specified part.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetSignPartResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "description": "Multipart upload session not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/assets": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "List Assets",
        "description": "Retrieves a list of Asset metadata. By default returns only organization-owned assets. Use source='template' to retrieve template assets instead. Supports pagination and filtering by category and name.",
        "operationId": "listAssets",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SourceQueryParameter"
          },
          {
            "$ref": "#/components/parameters/AssetCategoryQueryParameter"
          },
          {
            "$ref": "#/components/parameters/AssetNameQueryParameter"
          },
          {
            "$ref": "#/components/parameters/LimitQueryParameter"
          },
          {
            "$ref": "#/components/parameters/OffsetQueryParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of Asset metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          }
        }
      },
      "post": {
        "tags": [
          "Assets"
        ],
        "summary": "Finalize Multipart Upload and Create Asset Record",
        "description": "Completes a multipart upload by combining all uploaded parts and creates the Asset\nmetadata record in the Core API. The client must provide all part numbers and their\ncorresponding ETags from the S3 upload responses. The Core API will complete the\nmultipart upload in S3 and verify the final object before creating the database record.",
        "operationId": "createAssetRecord",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Finalization details for the multipart asset upload.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssetFinalizationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Multipart upload completed successfully and Asset record created in DB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "description": "Multipart upload session not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          }
        }
      }
    },
    "/assets/{asset_id}/download": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "Retrieve Asset Metadata and Download URL",
        "description": "Retrieves metadata for a specific Asset, including a pre-signed URL for downloading the content. The user can access an asset if it belongs to their organization or to the shared template organization.",
        "operationId": "getAssetDownloadById",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AssetIdPathParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully retrieved Asset metadata, including a download URL.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Asset"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "asset_url": {
                          "type": "string",
                          "format": "uri",
                          "readOnly": true,
                          "description": "A temporary, pre-signed URL to download the asset's content. This URL will expire.",
                          "example": "https://s3.amazonaws.com/ojin-assets/asset.mp4?X-Amz-Algorithm=..."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/assets/{asset_id}": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "Retrieve Asset Metadata",
        "description": "Retrieves metadata for a specific Asset. Does not include a download URL. The user can access an asset if it belongs to their organization or to the shared template organization.",
        "operationId": "getAssetById",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AssetIdPathParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully retrieved Asset metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      },
      "delete": {
        "tags": [
          "Assets"
        ],
        "summary": "Delete an Asset",
        "description": "Permanently deletes an Asset's metadata from the DB and its corresponding object from S3 (hard delete). This operation is restricted to assets owned by the user's organization and cannot be used on shared template assets.",
        "operationId": "deleteAsset",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AssetIdPathParameter"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContentSuccess"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/assets/generate/ojin/oris-1.0-idle-video-generator": {
      "post": {
        "tags": [
          "Assets"
        ],
        "summary": "Generate idle video from image asset",
        "description": "Triggers background generation of an idle video from a source image asset\nusing the ojin/oris-1.0 idle video generator.\n\nThe job runs asynchronously and returns a `job_id` for status tracking.\n\nOn completion, the generated video is saved as a new Asset in the organization.\nThe `result.asset_id` field in the job object will contain the new asset ID.",
        "operationId": "generateIdleVideo",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdleVideoGenerationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted and queued for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdleVideoGenerationResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "description": "Source asset not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/public/model-configs/{model_config_id}": {
      "get": {
        "tags": [
          "Public API"
        ],
        "summary": "Retrieve a Model Configuration (Public)",
        "description": "Retrieves a specific Model Configuration by ID. This is the public variant of\n`GET /model-configs/{model_config_id}`, designed for use by the Ojin widget\nembedded on third-party domains. Uses permissive CORS and requires no authentication.\nResources are accessed by their UUID.\n\nInternal fields (organization_id, created_by) are stripped from the response.",
        "operationId": "publicGetModelConfigById",
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelConfigIdPathParameter"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Model Configuration details (without internal fields).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicModelConfig"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/public/assets/{asset_id}": {
      "get": {
        "tags": [
          "Public API"
        ],
        "summary": "Retrieve Asset Metadata with Download URL (Public)",
        "description": "Retrieves metadata for a specific Asset, including a pre-signed download URL.\nThis is the public variant of `GET /assets/{asset_id}`, designed for use by the\nOjin widget embedded on third-party domains. Uses permissive CORS and requires\nno authentication. Resources are accessed by their UUID.\n\nInternal fields (organization_id, created_by) are stripped from the response.",
        "operationId": "publicGetAssetById",
        "parameters": [
          {
            "$ref": "#/components/parameters/AssetIdPathParameter"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Successfully retrieved Asset metadata with download URL (without internal fields).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAsset"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Asset": {
        "type": "object",
        "description": "Represents a digital asset managed by the Core API.",
        "properties": {
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the Asset, generated during upload initiation.",
            "readOnly": true,
            "example": "123e4567-e89b-12d3-a456-426614174000"
          },
          "organization_id": {
            "type": "string",
            "description": "ID of the Organisation (from external IdP) that owns this Asset. Server-set.",
            "readOnly": true,
            "example": "org_123abc"
          },
          "created_by": {
            "type": "string",
            "description": "User ID of the creator (from external IdP). Server-set.",
            "readOnly": true,
            "example": "user_789def"
          },
          "name": {
            "type": "string",
            "description": "The original file name of the Asset. NOT NULL.",
            "example": "product_promo.mp4"
          },
          "category": {
            "type": "string",
            "description": "Primary category of the Asset (e.g., 'video', 'image', 'weight'). NOT NULL. List of values managed in code.",
            "example": "video"
          },
          "content_type": {
            "type": "string",
            "description": "The MIME type of the asset. NOT NULL.",
            "example": "video/mp4"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "description": "The size of the asset in bytes. NOT NULL.",
            "example": 10485760
          },
          "etag": {
            "type": "string",
            "description": "The ETag of the S3 object, used for integrity checking. NOT NULL.",
            "example": "\"d41d8cd98f00b204e9800998ecf8427e\""
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of when this Asset record was created (finalized). Server-generated. Defaults to CURRENT_TIMESTAMP.",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of the last update to this Asset record. Server-generated. Auto-updates on modification.",
            "readOnly": true
          }
        },
        "required": [
          "asset_id",
          "organization_id",
          "created_by",
          "name",
          "category",
          "content_type",
          "size_bytes",
          "etag",
          "created_at",
          "updated_at"
        ]
      },
      "AssetFinalizationRequest": {
        "type": "object",
        "description": "Payload to finalize a multipart asset upload and create the asset metadata record in DB.",
        "properties": {
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "The unique ID received from the 'initiate-upload' step."
          },
          "upload_id": {
            "type": "string",
            "description": "The multipart upload ID from the 'initiate-upload' step."
          },
          "name": {
            "type": "string",
            "description": "The original filename (must be consistent with initiate request)."
          },
          "category": {
            "type": "string",
            "description": "The asset category (must be consistent with initiate request)."
          },
          "content_type": {
            "type": "string",
            "description": "Final confirmed MIME type of the asset."
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "description": "Final confirmed size of the asset in bytes."
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssetUploadPart"
            },
            "description": "List of all uploaded parts with their ETags, in order.",
            "minItems": 1
          }
        },
        "required": [
          "asset_id",
          "upload_id",
          "name",
          "category",
          "content_type",
          "size_bytes",
          "parts"
        ]
      },
      "AssetInitiateUploadRequest": {
        "type": "object",
        "description": "Payload to initiate a multipart asset upload.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Original filename of the asset.",
            "example": "conference_highlights.mov"
          },
          "category": {
            "type": "string",
            "description": "Category for the asset (e.g., 'video', 'image').",
            "example": "video"
          },
          "content_type": {
            "type": "string",
            "nullable": true,
            "description": "Client-declared MIME type of the file.",
            "example": "video/quicktime"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "description": "Client-declared file size in bytes.",
            "example": 52428800
          }
        },
        "required": [
          "name",
          "category"
        ]
      },
      "AssetInitiateUploadResponse": {
        "type": "object",
        "description": "Response from initiating a multipart asset upload.",
        "properties": {
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "A unique ID generated by Core API for this asset transaction."
          },
          "upload_id": {
            "type": "string",
            "description": "The multipart upload ID from S3, used to identify this multipart upload session."
          },
          "s3_key": {
            "type": "string",
            "description": "The S3 key for this asset."
          }
        },
        "required": [
          "asset_id",
          "upload_id"
        ]
      },
      "AssetSignPartRequest": {
        "type": "object",
        "description": "Payload to get a pre-signed URL for uploading a specific part of a multipart upload.",
        "properties": {
          "s3_key": {
            "type": "string",
            "description": "The S3 key for this asset."
          },
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "The asset ID from the initiate upload response."
          },
          "upload_id": {
            "type": "string",
            "description": "The multipart upload ID from the initiate upload response."
          },
          "part_number": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "The part number for this upload part (1-10000)."
          }
        },
        "required": [
          "asset_id",
          "upload_id",
          "part_number"
        ]
      },
      "AssetSignPartResponse": {
        "type": "object",
        "description": "Response containing the pre-signed URL for uploading a specific part.",
        "properties": {
          "upload_url": {
            "type": "string",
            "format": "url",
            "description": "The pre-signed S3 URL to PUT this specific part to."
          },
          "part_number": {
            "type": "integer",
            "description": "The part number this URL is for."
          }
        },
        "required": [
          "upload_url",
          "part_number"
        ]
      },
      "AssetUploadPart": {
        "type": "object",
        "description": "Information about a completed upload part.",
        "properties": {
          "part_number": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "The part number that was uploaded."
          },
          "etag": {
            "type": "string",
            "description": "The ETag returned by S3 after successfully uploading this part."
          }
        },
        "required": [
          "part_number",
          "etag"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "A short, machine-readable error code string.",
            "example": "RESOURCE_NOT_FOUND"
          },
          "message": {
            "type": "string",
            "description": "A human-readable description of the error.",
            "example": "The requested model configuration was not found."
          },
          "details": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Optional. Additional structured details about the error.",
            "example": {
              "resource_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
            }
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "IdleVideoGenerationRequest": {
        "type": "object",
        "description": "Request payload to trigger idle video generation.",
        "properties": {
          "source_asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "The ID of the source image asset to use for generating the idle video.",
            "example": "123e4567-e89b-12d3-a456-426614174000"
          },
          "reference_template": {
            "type": "string",
            "description": "The reference motion template to use (e.g., 'v1', 'v2', 'v3').",
            "enum": [
              "v1",
              "v2",
              "v3"
            ],
            "example": "v1"
          }
        },
        "required": [
          "source_asset_id"
        ]
      },
      "IdleVideoGenerationResponse": {
        "type": "object",
        "description": "Response from triggering idle video generation.",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifier of the background job created for this generation request.",
            "example": "123e4567-e89b-12d3-a456-426614174000"
          }
        },
        "required": [
          "job_id"
        ]
      },
      "ModelConfig": {
        "type": "object",
        "description": "Represents a specific configuration of a ModelVariant.",
        "properties": {
          "model_config_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "Server-generated unique ID."
          },
          "organization_id": {
            "type": "string",
            "readOnly": true,
            "description": "Owning organization ID (from external IdP)."
          },
          "model_id": {
            "type": "string",
            "readOnly": true,
            "description": "ID of the parent Model, derived from the Model Variant."
          },
          "model_variant_id": {
            "type": "string",
            "description": "ID of the ModelVariant being configured. NOT NULL."
          },
          "created_by": {
            "type": "string",
            "readOnly": true,
            "description": "Creator's user ID (from external IdP)."
          },
          "title": {
            "type": "string",
            "description": "Title for the configuration. NOT NULL, unique per organization_id."
          },
          "model_configurations": {
            "type": "object",
            "additionalProperties": true,
            "description": "Parameters for the model variant, adhering to its schema. NOT NULL, defaults to '{}'.",
            "default": {}
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of creation. Defaults to CURRENT_TIMESTAMP."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of last update. Auto-updates."
          }
        },
        "required": [
          "model_config_id",
          "organization_id",
          "model_id",
          "model_variant_id",
          "created_by",
          "title",
          "model_configurations",
          "created_at",
          "updated_at"
        ]
      },
      "ModelConfigCreationRequest": {
        "type": "object",
        "description": "Payload for creating a new Model Configuration.",
        "properties": {
          "title": {
            "type": "string"
          },
          "model_variant_id": {
            "type": "string"
          },
          "model_configurations": {
            "type": "object",
            "additionalProperties": true,
            "default": {}
          }
        },
        "required": [
          "title",
          "model_variant_id"
        ]
      },
      "ModelConfigUpdateRequest": {
        "type": "object",
        "description": "Payload for updating a Model Configuration (title and parameters only).",
        "properties": {
          "title": {
            "type": "string"
          },
          "model_configurations": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "title",
          "model_configurations"
        ]
      },
      "ModelVariant": {
        "type": "object",
        "description": "Represents a specific variant of a Model.",
        "properties": {
          "model_variant_id": {
            "type": "string",
            "description": "Client-provided unique identifier (e.g., \"ojin/oris-v1/standard\").",
            "example": "ojin/oris-1.0/standard"
          },
          "model_id": {
            "type": "string",
            "description": "Identifier of the parent Model. NOT NULL.",
            "example": "ojin/oris-1.0"
          },
          "title": {
            "type": "string",
            "description": "Display name for the variant. NOT NULL, unique per model_id.",
            "example": "Standard Quality"
          },
          "description": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "UI display texts. NOT NULL, defaults to '{}'.",
            "example": {
              "resolution": "1024x1024"
            }
          },
          "preview_media_url": {
            "type": "string",
            "format": "url",
            "nullable": true,
            "description": "URL for a preview media.",
            "example": "https://cdn.ojin.ai/previews/variant.jpg"
          },
          "configuration_schema": {
            "type": "object",
            "description": "JSON schema for ModelConfig.model_configurations. NOT NULL, defaults to '{}'.",
            "example": {
              "type": "object",
              "properties": {
                "param1": {
                  "type": "string"
                }
              }
            },
            "additionalProperties": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of descriptive tags. NOT NULL, defaults to '[]'.",
            "example": [
              "general-purpose",
              "real-time"
            ]
          },
          "status": {
            "type": "string",
            "description": "Status (e.g., 'available', 'deprecated'). NOT NULL. List managed in code.",
            "example": "available"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of creation. Server-generated. Defaults to CURRENT_TIMESTAMP.",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of last update. Server-generated. Auto-updates.",
            "readOnly": true
          }
        },
        "required": [
          "model_variant_id",
          "model_id",
          "title",
          "description",
          "configuration_schema",
          "tags",
          "status",
          "created_at",
          "updated_at"
        ]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "description": "The number of items returned in the current page.",
            "example": 20
          },
          "offset": {
            "type": "integer",
            "description": "The number of items skipped before starting the current page.",
            "example": 0
          },
          "total_items": {
            "type": "integer",
            "description": "The total number of items available that match the query.",
            "example": 150
          }
        },
        "required": [
          "limit",
          "offset",
          "total_items"
        ]
      },
      "PublicAsset": {
        "type": "object",
        "description": "Public variant of Asset for widget embedding.\nStrips internal fields (organization_id, created_by) and includes a pre-signed download URL.",
        "properties": {
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "Unique identifier for the Asset.",
            "example": "123e4567-e89b-12d3-a456-426614174000"
          },
          "name": {
            "type": "string",
            "description": "The original file name of the Asset.",
            "example": "product_promo.mp4"
          },
          "category": {
            "type": "string",
            "description": "Primary category of the Asset (e.g., 'video', 'image', 'weight').",
            "example": "video"
          },
          "content_type": {
            "type": "string",
            "description": "The MIME type of the asset.",
            "example": "video/mp4"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "description": "The size of the asset in bytes.",
            "example": 10485760
          },
          "etag": {
            "type": "string",
            "description": "The ETag of the S3 object.",
            "example": "\"d41d8cd98f00b204e9800998ecf8427e\""
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of creation."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of last update."
          },
          "asset_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true,
            "description": "A temporary, pre-signed URL to download the asset's content. This URL will expire.",
            "example": "https://s3.amazonaws.com/ojin-assets/asset.mp4?X-Amz-Algorithm=..."
          }
        },
        "required": [
          "asset_id",
          "name",
          "category",
          "content_type",
          "size_bytes",
          "etag",
          "created_at",
          "updated_at",
          "asset_url"
        ]
      },
      "PublicModelConfig": {
        "type": "object",
        "description": "Public variant of ModelConfig for widget embedding.\nStrips internal fields (organization_id, created_by) that are not needed by the widget.",
        "properties": {
          "model_config_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "Server-generated unique ID."
          },
          "model_id": {
            "type": "string",
            "readOnly": true,
            "description": "ID of the parent Model, derived from the Model Variant."
          },
          "model_variant_id": {
            "type": "string",
            "description": "ID of the ModelVariant being configured."
          },
          "title": {
            "type": "string",
            "description": "Title for the configuration."
          },
          "model_configurations": {
            "type": "object",
            "additionalProperties": true,
            "description": "Parameters for the model variant.",
            "default": {}
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of creation."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Timestamp of last update."
          }
        },
        "required": [
          "model_config_id",
          "model_id",
          "model_variant_id",
          "title",
          "model_configurations",
          "created_at",
          "updated_at"
        ]
      }
    },
    "parameters": {
      "AssetCategoryQueryParameter": {
        "name": "category",
        "in": "query",
        "required": false,
        "description": "Filter assets by category.",
        "schema": {
          "type": "string"
        }
      },
      "AssetIdPathParameter": {
        "name": "asset_id",
        "in": "path",
        "required": true,
        "description": "The unique identifier (UUID) of the Asset.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AssetNameQueryParameter": {
        "name": "name",
        "in": "query",
        "required": false,
        "description": "Filter assets by name (e.g., for partial match - specific behavior TBD by implementation).",
        "schema": {
          "type": "string"
        }
      },
      "LimitQueryParameter": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Number of items to return per page.",
        "schema": {
          "type": "integer",
          "default": 20,
          "minimum": 1,
          "maximum": 100
        }
      },
      "ModelConfigIdPathParameter": {
        "name": "model_config_id",
        "in": "path",
        "required": true,
        "description": "The unique identifier (UUID) of the Model Configuration.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ModelIdQueryParameter": {
        "name": "model_id",
        "in": "query",
        "required": false,
        "description": "Filter model variants by parent model_id.",
        "schema": {
          "type": "string"
        }
      },
      "ModelVariantIdPathParameter": {
        "name": "model_variant_id",
        "in": "path",
        "required": true,
        "description": "The unique identifier of the model variant.",
        "schema": {
          "type": "string"
        }
      },
      "ModelVariantIdQueryParameter": {
        "name": "model_variant_id",
        "in": "query",
        "required": false,
        "description": "Filter model configurations by the model_variant_id.",
        "schema": {
          "type": "string"
        }
      },
      "OffsetQueryParameter": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Number of items to skip for pagination.",
        "schema": {
          "type": "integer",
          "default": 0,
          "minimum": 0
        }
      },
      "ReferencedByQueryParameter": {
        "name": "referenced_by",
        "in": "query",
        "required": false,
        "description": "Delete reference to the agent configuration.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "SourceQueryParameter": {
        "name": "source",
        "in": "query",
        "required": false,
        "description": "Filter items by ownership source. Use 'org' for items owned by the user's organization, 'template' for items from the template organization. Defaults to 'org'.",
        "schema": {
          "type": "string",
          "enum": [
            "org",
            "template"
          ],
          "default": "org"
        }
      },
      "StatusQueryParameter": {
        "name": "status",
        "in": "query",
        "required": false,
        "description": "Filter by status.",
        "schema": {
          "type": "string"
        }
      },
      "TagsQueryParameter": {
        "name": "tags",
        "in": "query",
        "required": false,
        "description": "Filter model variants by a comma-separated list of tags (AND logic).",
        "style": "form",
        "explode": false,
        "schema": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequestError": {
        "description": "Invalid request payload or parameters.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ConflictError": {
        "description": "Conflict with the current state of the resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ForbiddenError": {
        "description": "Authenticated principal does not have permission.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NoContentSuccess": {
        "description": "Operation successful, no content to return."
      },
      "NotFoundError": {
        "description": "The requested resource was not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "UnauthorizedError": {
        "description": "Authentication token is missing, invalid, or expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "APIKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for authenticated access to the Ojin REST API."
      }
    }
  }
}
