{
  "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."
    },
    {
      "name": "Agents",
      "description": "Operations related to Agents (Agent Service v1) — configuration, providers, and call history."
    }
  ],
  "paths": {
    "/model-variants": {
      "get": {
        "tags": [
          "Model Variants"
        ],
        "summary": "List all Model Variants",
        "description": "List all Model Variants.\nPublic callers, including API-key-authenticated callers, only receive variants with status=\"public\",\nignoring 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.\nPublic callers, including API-key-authenticated callers, can retrieve the variant only if status=\"public\".\nOtherwise 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/ModelVariantTagQueryParameter"
          },
          {
            "$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"
                }
              }
            }
          }
        }
      }
    },
    "/agents/providers": {
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "List STS providers",
        "description": "Lists available STS (speech-to-speech) providers and the fields each expects.",
        "operationId": "listAgentProviders",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Provider catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AgentProvider"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          }
        }
      }
    },
    "/agents": {
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "List Agents (org-scoped)",
        "operationId": "listAgents",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitQueryParameter"
          },
          {
            "$ref": "#/components/parameters/OffsetQueryParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of Agents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentBase"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "total_items": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "limit",
                        "offset",
                        "total_items"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          }
        }
      },
      "post": {
        "tags": [
          "Agents"
        ],
        "summary": "Create a new Agent",
        "description": "Creates an Agent, optionally seeded from a preset. On creation the response includes `auth_secret` (one-shot) when `auth_enabled` is true.",
        "operationId": "createAgent",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Agent created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          }
        }
      }
    },
    "/agents/{agent_id}": {
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "Retrieve an Agent",
        "operationId": "getAgent",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      },
      "delete": {
        "tags": [
          "Agents"
        ],
        "summary": "Delete an Agent",
        "description": "Deletes an Agent. Fails with 409 if there are active sessions for the agent.",
        "operationId": "deleteAgent",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContentSuccess"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          }
        }
      },
      "patch": {
        "tags": [
          "Agents"
        ],
        "summary": "Update an Agent (partial)",
        "operationId": "updateAgent",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAgentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/agents/{agent_id}/status": {
      "patch": {
        "tags": [
          "Agents"
        ],
        "summary": "Publish or unpublish an Agent",
        "operationId": "updateAgentStatus",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAgentStatusRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent status updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/agents/{agent_id}/calls": {
      "get": {
        "tags": [
          "Agents"
        ],
        "summary": "List call history for an Agent",
        "operationId": "listAgentCalls",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          },
          {
            "$ref": "#/components/parameters/LimitQueryParameter"
          },
          {
            "$ref": "#/components/parameters/OffsetQueryParameter"
          },
          {
            "name": "started_after",
            "in": "query",
            "required": false,
            "description": "ISO-8601 date/datetime lower bound on `started_at`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "started_before",
            "in": "query",
            "required": false,
            "description": "ISO-8601 date/datetime upper bound on `started_at`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated call history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CallRecord"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "total_items": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "limit",
                        "offset",
                        "total_items"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        }
      }
    },
    "/agents/{agent_id}/verify-provider": {
      "post": {
        "tags": [
          "Agents"
        ],
        "summary": "Verify third-party provider credentials for an Agent",
        "description": "Makes a live API call to the agent's configured STS provider using the stored\nAPI key and config ID. Returns whether the credentials are valid and any error\ndetail. Does not modify the agent.",
        "operationId": "verifyAgentProvider",
        "security": [
          {
            "APIKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdPathParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result (may be valid or invalid).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyProviderResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayError"
          }
        }
      }
    },
    "/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, including a temporary pre-signed `asset_url` for download. 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, model_id) are stripped from the response. The widget chains through ``model_variant_id`` and calls ``GET /public/model-variants/{model_variant_id}`` for catalog metadata (rendered frame size, etc.).",
        "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/model-variants/{model_variant_id}": {
      "get": {
        "tags": [
          "Public API"
        ],
        "summary": "Retrieve a Model Variant (Public)",
        "description": "Retrieves a specific Model Variant by ID. Designed for use by the Ojin widget\nembedded on third-party domains: after fetching a model_config via\n``GET /public/model-configs/{model_config_id}``, the widget calls this with the\nreturned ``model_variant_id`` to read catalog defaults declared on the variant —\nmost importantly ``description.output_width`` / ``description.output_height``,\nwhich it uses to size its inline ``aspect-ratio``.\n\n``model.description`` (on the parent ``Model`` row, used internally for\n``AGENT_MODAL_ORCHESTRATOR_URL_OVERRIDE``) is deliberately not surfaced.\n\nUses permissive CORS and requires no authentication.",
        "operationId": "publicGetModelVariantById",
        "parameters": [
          {
            "$ref": "#/components/parameters/ModelVariantIdPathParameter"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Model Variant details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicModelVariant"
                }
              }
            }
          },
          "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}/download`, 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": {
      "AgentAdvanced": {
        "type": "object",
        "description": "Advanced configuration for an Agent (video pipeline + LLM tuning).",
        "properties": {
          "max_video_source_dimension": {
            "type": "integer",
            "minimum": 1,
            "nullable": true
          },
          "llm_max_tokens": {
            "type": "integer",
            "minimum": 64,
            "maximum": 4096,
            "nullable": true,
            "description": "Maximum completion tokens the managed (Ojin-mode) LLM may generate per spoken reply. Raise it if answers get cut off mid-sentence; lower it to keep replies short. Defaults to 384 when unset."
          }
        }
      },
      "AgentBase": {
        "type": "object",
        "description": "Basic representation of an Agent for list views.",
        "properties": {
          "agent_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "created_by": {
            "type": "string",
            "readOnly": true,
            "description": "Creator's user ID (from external IdP), or `api_key:<id>` when the resource was created via an API key."
          },
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "mode": {
            "$ref": "#/components/schemas/AgentMode"
          },
          "status": {
            "$ref": "#/components/schemas/AgentStatus"
          },
          "face_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "agent_id",
          "organization_id",
          "created_by",
          "title",
          "mode",
          "status",
          "face_id",
          "created_at",
          "updated_at"
        ]
      },
      "AgentBehaviour": {
        "type": "object",
        "description": "Conversational behaviour knobs applied to an Agent.",
        "properties": {
          "greeting": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "greeting_reference": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "greeting_mode": {
            "type": "string",
            "enum": [
              "verbatim",
              "composed"
            ],
            "nullable": true
          },
          "nudge_message": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "nudge_reference": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "nudge_mode": {
            "type": "string",
            "enum": [
              "verbatim",
              "composed"
            ],
            "nullable": true
          },
          "farewell_message": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "farewell_reference": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          },
          "farewell_mode": {
            "type": "string",
            "enum": [
              "verbatim",
              "composed"
            ],
            "nullable": true
          },
          "max_conversation_duration_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400,
            "nullable": true
          },
          "inactivity_nudge_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600,
            "nullable": true
          },
          "allow_interruption": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "AgentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AgentBase"
          },
          {
            "type": "object",
            "description": "Full Agent detail including configuration, STS credentials flags, LLM tool wiring.",
            "properties": {
              "language": {
                "$ref": "#/components/schemas/AgentLanguage"
              },
              "system_prompt": {
                "type": "string",
                "maxLength": 10000,
                "nullable": true
              },
              "tts_provider": {
                "type": "string"
              },
              "voice_id": {
                "type": "string",
                "maxLength": 255,
                "nullable": true
              },
              "behaviour": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AgentBehaviour"
                  }
                ],
                "nullable": true
              },
              "advanced": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AgentAdvanced"
                  }
                ],
                "nullable": true
              },
              "sts_provider": {
                "type": "string",
                "maxLength": 100,
                "nullable": true
              },
              "sts_config_id": {
                "type": "string",
                "maxLength": 255,
                "nullable": true
              },
              "sts_api_key_set": {
                "type": "boolean",
                "description": "True when an encrypted STS API key is persisted for this agent. The raw key is never returned."
              },
              "provider_config": {
                "type": "object",
                "additionalProperties": true,
                "nullable": true
              },
              "preview_url": {
                "type": "string",
                "maxLength": 2048,
                "nullable": true,
                "description": "A direct URL or a bare asset-ID UUID. The public agent endpoint resolves UUIDs to signed S3 URLs before returning them to clients."
              },
              "starts_conversation": {
                "type": "boolean"
              },
              "session_limit_seconds": {
                "type": "integer",
                "minimum": 1,
                "maximum": 86400
              },
              "max_concurrency": {
                "oneOf": [
                  {
                    "type": "integer",
                    "enum": [
                      -1
                    ]
                  },
                  {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                ],
                "description": "Maximum concurrent sessions. -1 means unbounded (no limit); valid positive values are 1–100."
              },
              "auth_enabled": {
                "type": "boolean"
              },
              "auth_secret_set": {
                "type": "boolean",
                "description": "True when an auth secret is persisted."
              },
              "auth_secret": {
                "type": "string",
                "nullable": true,
                "description": "Plaintext auth secret. Only returned to the creator immediately after\nagent creation (one-shot). Subsequent reads return null; use auth_secret_set\nto detect whether a secret is configured."
              },
              "allowed_hostnames": {
                "type": "array",
                "nullable": true,
                "items": {
                  "type": "string",
                  "maxLength": 255
                }
              },
              "tools": {
                "type": "array",
                "description": "Per-tool function-calling definitions with delivery + enabled flag.",
                "items": {
                  "$ref": "#/components/schemas/AgentTool"
                }
              }
            },
            "required": [
              "language",
              "tts_provider",
              "sts_api_key_set",
              "starts_conversation",
              "session_limit_seconds",
              "max_concurrency",
              "auth_enabled",
              "auth_secret_set",
              "tools"
            ]
          }
        ]
      },
      "AgentLanguage": {
        "type": "string",
        "enum": [
          "en",
          "de",
          "fr",
          "es",
          "ar",
          "ja"
        ],
        "description": "Spoken language code for the agent. Drives Deepgram STT language,\nElevenLabs voice filtering in the dashboard, and an LLM\nsystem-prompt suffix that pins the response language.\n"
      },
      "AgentMode": {
        "type": "string",
        "enum": [
          "ojin",
          "third_party"
        ],
        "description": "The operating mode of an agent. Ojin mode uses the built-in pipeline; third_party mode delegates to an external STS provider."
      },
      "AgentProvider": {
        "type": "object",
        "description": "STS provider descriptor (returned by GET /agents/providers).",
        "properties": {
          "slug": {
            "type": "string",
            "example": "elevenlabs"
          },
          "label": {
            "type": "string",
            "example": "ElevenLabs"
          },
          "required_persisted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Provider-specific fields that must be persisted in provider_config."
          },
          "optional_persisted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Provider-specific fields that may be persisted in provider_config."
          },
          "session_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Provider-specific fields that must be supplied per-session."
          },
          "status": {
            "type": "string",
            "example": "available"
          }
        },
        "required": [
          "slug",
          "label",
          "required_persisted",
          "optional_persisted",
          "session_fields",
          "status"
        ]
      },
      "AgentStatus": {
        "type": "string",
        "enum": [
          "published",
          "unpublished"
        ],
        "description": "The publication status of an agent. Published agents are available for connections; unpublished agents are not."
      },
      "AgentTool": {
        "type": "object",
        "additionalProperties": false,
        "description": "A function-calling tool exposed by the agent to the LLM, with per-tool delivery and enabled flag.",
        "properties": {
          "tool_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "enabled": {
            "type": "boolean",
            "default": true,
            "description": "When false, the tool is hidden from the LLM but its delivery config is preserved."
          },
          "type": {
            "type": "string",
            "enum": [
              "function"
            ],
            "default": "function"
          },
          "function": {
            "$ref": "#/components/schemas/ToolFunction"
          },
          "delivery": {
            "$ref": "#/components/schemas/ToolDelivery"
          }
        },
        "required": [
          "tool_id",
          "enabled",
          "type",
          "function",
          "delivery"
        ]
      },
      "AgentToolInput": {
        "type": "object",
        "additionalProperties": false,
        "description": "Input shape for an agent tool. `tool_id` is optional — when present and\nmatched (by `function.name`) the existing row is updated in place; when\nabsent or unmatched a new tool is created. `enabled` defaults to true\nand `type` defaults to \"function\" server-side when omitted.",
        "properties": {
          "tool_id": {
            "type": "string",
            "format": "uuid"
          },
          "enabled": {
            "type": "boolean"
          },
          "type": {
            "type": "string",
            "enum": [
              "function"
            ]
          },
          "function": {
            "$ref": "#/components/schemas/ToolFunction"
          },
          "delivery": {
            "$ref": "#/components/schemas/ToolDelivery"
          }
        },
        "required": [
          "function",
          "delivery"
        ]
      },
      "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), or `api_key:<id>` when created via an API key. 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
          },
          "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",
          "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"
        ]
      },
      "CallRecord": {
        "type": "object",
        "description": "A single historical call for an Agent (one call = one agent session).",
        "properties": {
          "call_id": {
            "type": "string",
            "format": "uuid"
          },
          "agent_id": {
            "type": "string",
            "format": "uuid"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "session_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "applied_session_limit_seconds": {
            "type": "integer"
          },
          "client_user_ref": {
            "type": "string",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "e.g. 'active', 'completed', 'failed'"
          },
          "termination_reason": {
            "type": "string",
            "nullable": true
          },
          "failure_reason": {
            "type": "string",
            "nullable": true
          },
          "participant_count": {
            "type": "integer"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          }
        },
        "required": [
          "call_id",
          "agent_id",
          "organization_id",
          "applied_session_limit_seconds",
          "started_at",
          "status",
          "participant_count"
        ]
      },
      "ClientToolDelivery": {
        "type": "object",
        "additionalProperties": false,
        "description": "Tool calls are delivered to the in-browser widget as an `ojinToolCall` CustomEvent.",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "client"
            ]
          }
        },
        "required": [
          "mode"
        ]
      },
      "CreateAgentRequest": {
        "type": "object",
        "description": "Payload for creating a new Agent, optionally from a preset.",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "mode": {
            "$ref": "#/components/schemas/AgentMode"
          },
          "preset_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional preset to seed the new agent from."
          }
        },
        "required": [
          "title"
        ]
      },
      "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"
          },
          "model_variant_id": {
            "type": "string",
            "maxLength": 128,
            "description": "Model variant the generated idle video is intended for (e.g. 'ojin/oris-portrait'). Used only to label the background job so the Recent Generations list names the right model. Optional; callers without a model context omit it and the job uses a default label.",
            "example": "ojin/oris-portrait"
          },
          "target_model_config_id": {
            "type": "string",
            "format": "uuid",
            "description": "Model config to assign the generated video to as its active idle preview once the job completes. When set, core-api writes model_configurations.preview server-side on completion, so the assignment lands even if the browser closed, navigated away, or refreshed mid-generation. Must be a config owned by the caller's organization. Optional; omit to only create the video without assigning it.",
            "example": "123e4567-e89b-12d3-a456-426614174000"
          }
        },
        "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), or `api_key:<id>` when created via an API key."
          },
          "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, model_id) — the widget\nchains through ``model_variant_id`` and calls ``GET /public/model-variants/{model_variant_id}``\nwhen it needs the parent ``Model`` reference.",
        "properties": {
          "model_config_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "Server-generated unique ID."
          },
          "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_variant_id",
          "title",
          "model_configurations",
          "created_at",
          "updated_at"
        ]
      },
      "PublicModelVariant": {
        "type": "object",
        "description": "Public variant of ModelVariant for widget embedding. Returns every\n``model_variant`` column — including ``description``, which is the\npublic-defaults bag the widget reads for ``output_width`` /\n``output_height`` to size its host. The parent ``Model``'s own\n``description`` (which holds internal routing values like\n``AGENT_MODAL_ORCHESTRATOR_URL_OVERRIDE``) is intentionally NOT\nsurfaced — it lives on a different row and is never JOINed in here.",
        "properties": {
          "model_variant_id": {
            "type": "string",
            "description": "Client-provided unique identifier."
          },
          "model_id": {
            "type": "string",
            "description": "Identifier of the parent Model."
          },
          "title": {
            "type": "string",
            "description": "Display name."
          },
          "description": {
            "type": "object",
            "additionalProperties": true,
            "description": "Public catalog defaults consumed by widgets (e.g. output_width, output_height, playground_type)."
          },
          "preview_media_url": {
            "type": "string",
            "format": "url",
            "nullable": true
          },
          "configuration_schema": {
            "type": "object",
            "additionalProperties": true,
            "description": "JSON schema for the user-tunable knobs on ModelConfig.model_configurations."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "model_variant_id",
          "model_id",
          "title",
          "description",
          "configuration_schema",
          "tags",
          "status",
          "created_at",
          "updated_at"
        ]
      },
      "ToolDelivery": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ClientToolDelivery"
          },
          {
            "$ref": "#/components/schemas/WebhookToolDelivery"
          }
        ],
        "discriminator": {
          "propertyName": "mode",
          "mapping": {
            "client": "#/components/schemas/ClientToolDelivery",
            "webhook": "#/components/schemas/WebhookToolDelivery"
          }
        }
      },
      "ToolFunction": {
        "type": "object",
        "additionalProperties": false,
        "description": "OpenAI / Anthropic tool function definition.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 64,
            "pattern": "^[a-zA-Z0-9_-]+$",
            "description": "Function name. Must match the LLM tool-name regex."
          },
          "description": {
            "type": "string",
            "maxLength": 1024,
            "nullable": true
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "JSON Schema for the function's parameters (opaque pass-through)."
          }
        },
        "required": [
          "name"
        ]
      },
      "UpdateAgentRequest": {
        "type": "object",
        "description": "Payload for updating an Agent (partial update — all fields optional).",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "mode": {
            "$ref": "#/components/schemas/AgentMode"
          },
          "face_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "language": {
            "$ref": "#/components/schemas/AgentLanguage"
          },
          "system_prompt": {
            "type": "string",
            "maxLength": 10000,
            "nullable": true
          },
          "tts_provider": {
            "type": "string",
            "enum": [
              "elevenlabs",
              "ojin"
            ]
          },
          "voice_id": {
            "type": "string",
            "maxLength": 255,
            "nullable": true
          },
          "behaviour": {
            "$ref": "#/components/schemas/AgentBehaviour"
          },
          "advanced": {
            "$ref": "#/components/schemas/AgentAdvanced"
          },
          "sts_provider": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "sts_api_key": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true,
            "writeOnly": true
          },
          "sts_config_id": {
            "type": "string",
            "maxLength": 255,
            "nullable": true
          },
          "provider_config": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          },
          "preview_url": {
            "type": "string",
            "maxLength": 2048,
            "nullable": true,
            "description": "A direct URL or a bare asset-ID UUID. The public agent endpoint resolves UUIDs to signed S3 URLs before returning them to clients."
          },
          "session_limit_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400
          },
          "max_concurrency": {
            "oneOf": [
              {
                "type": "integer",
                "enum": [
                  -1
                ]
              },
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              }
            ],
            "description": "Maximum concurrent sessions. -1 means unbounded (no limit); valid positive values are 1–100."
          },
          "starts_conversation": {
            "type": "boolean"
          },
          "auth_enabled": {
            "type": "boolean"
          },
          "allowed_hostnames": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 255
            }
          },
          "tools": {
            "type": "array",
            "description": "Replace the agent's tools with this array (full-replace semantics).\nOmit to leave tools unchanged. UPSERTs by `function.name`; tools not\npresent in the array are deleted from the agent.",
            "items": {
              "$ref": "#/components/schemas/AgentToolInput"
            }
          }
        }
      },
      "UpdateAgentStatusRequest": {
        "type": "object",
        "description": "Payload for toggling Agent publication status.",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/AgentStatus"
          }
        },
        "required": [
          "status"
        ]
      },
      "VerifyProviderResult": {
        "type": "object",
        "description": "Result of a live credential verification check against a third-party STS provider.",
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "provider": {
            "type": "string"
          },
          "error": {
            "type": "string",
            "description": "Human-readable reason when valid is false."
          }
        },
        "required": [
          "valid",
          "provider"
        ]
      },
      "WebhookToolDelivery": {
        "type": "object",
        "additionalProperties": false,
        "description": "Tool calls are POSTed to an external webhook URL.",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "webhook"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048
          },
          "wait_for_response": {
            "type": "boolean",
            "default": false,
            "description": "When true, the LLM waits for the webhook's response and incorporates it\ninto the conversation. When false, the webhook is fired-and-forgotten and\nthe LLM receives a neutral acknowledgement immediately."
          }
        },
        "required": [
          "mode",
          "url"
        ]
      }
    },
    "parameters": {
      "AgentIdPathParameter": {
        "name": "agent_id",
        "in": "path",
        "required": true,
        "description": "The unique identifier (UUID) of the Agent.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "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"
        }
      },
      "ModelVariantTagQueryParameter": {
        "name": "model_variant_tag",
        "in": "query",
        "required": false,
        "description": "Filter model configurations whose model variant contains this tag (e.g. \"_agent-tts\").",
        "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": {
      "BadGatewayError": {
        "description": "Upstream service (e.g. WebRTC room provider, agent orchestrator) returned an error or was unreachable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "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."
      }
    }
  }
}
