{
  "openapi": "3.1.0",
  "info": {
    "title": "Bioptimus Model Server API",
    "version": "1.0.0",
    "description": "REST API exposed by the Bioptimus Model Server. All model endpoints accept and return JSON; each request represents a single tile. The default port is 8080. The server auto-batches concurrent tile requests for GPU efficiency (up to 32 tiles per forward pass by default).\n\nThe same request/response schema is used on-premise (path-based endpoints below) and on AWS SageMaker (the `/invocations` dispatch endpoint). The `bioptimus` Python SDK wraps both — see the SDK guide.\n\nAuthentication: the Model Server REST API currently requires no authentication. Secure it via network isolation / private networking. (Auth is being clarified.)"
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Local Bioptimus Model Server (replace host/port for your deployment)"
    }
  ],
  "tags": [
    {
      "name": "Embedding",
      "description": "Tile feature extraction"
    },
    {
      "name": "Prediction",
      "description": "Spatial gene expression and tissue segmentation"
    },
    {
      "name": "Service",
      "description": "Health and discovery"
    }
  ],
  "paths": {
    "/api/embed/h1": {
      "post": {
        "tags": [
          "Embedding"
        ],
        "summary": "H-Optimus (H1) tile embedding",
        "description": "Compute H1 tile embeddings. Input: a 224×224 tile at 0.5 MPP. Output: a 1536-dimensional embedding vector in `output`.",
        "operationId": "embedH1",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelRequest"
              },
              "example": {
                "image_data": "<base64-png-224x224>",
                "slide_name": "slide_A",
                "x": 0,
                "y": 0,
                "width": 224,
                "height": 224,
                "tissue_ratio": 0.8,
                "patch_idx": 0,
                "resolution": 0.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (malformed or missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "500": {
            "description": "Inference error",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Internal server error"
                }
              }
            }
          },
          "503": {
            "description": "Model still loading or GPU unavailable",
            "content": {
              "application/json": {
                "example": {
                  "status": "loading"
                }
              }
            }
          }
        }
      }
    },
    "/api/embed/m-optimus": {
      "post": {
        "tags": [
          "Embedding"
        ],
        "summary": "M-Optimus tile embedding",
        "description": "Compute M-Optimus tile embeddings. Input: a 224×224 tile at 0.5 MPP. Output: an embedding vector in `output`.",
        "operationId": "embedMOptimus",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelRequest"
              },
              "example": {
                "image_data": "<base64-png-224x224>",
                "slide_name": "slide_A",
                "x": 0,
                "y": 0,
                "width": 224,
                "height": 224,
                "tissue_ratio": 0.8,
                "patch_idx": 0,
                "resolution": 0.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (malformed or missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "500": {
            "description": "Inference error",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Internal server error"
                }
              }
            }
          },
          "503": {
            "description": "Model still loading or GPU unavailable",
            "content": {
              "application/json": {
                "example": {
                  "status": "loading"
                }
              }
            }
          }
        }
      }
    },
    "/api/predict/m-optimus": {
      "post": {
        "tags": [
          "Prediction"
        ],
        "summary": "M-Optimus spatial gene expression prediction",
        "description": "Predict spatial gene expression from a tile and optional bulk RNA counts. Input: a 224×224 tile at 0.5 MPP, plus an optional `bulk_rna` vector aligned to the model's input gene set (see GET /api/metadata/m-optimus). When `bulk_rna` is omitted, a zero vector is used (H&E-only mode). Output: predicted expression for each output gene in `output`.",
        "operationId": "predictMOptimus",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelRequest"
              },
              "example": {
                "image_data": "<base64-png-224x224>",
                "bulk_rna": [
                  12.3,
                  4.5,
                  8.7
                ],
                "slide_name": "slide_A",
                "x": 0,
                "y": 0,
                "width": 224,
                "height": 224,
                "tissue_ratio": 0.8,
                "patch_idx": 0,
                "resolution": 0.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prediction result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (malformed or missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "500": {
            "description": "Inference error",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Internal server error"
                }
              }
            }
          },
          "503": {
            "description": "Model still loading or GPU unavailable",
            "content": {
              "application/json": {
                "example": {
                  "status": "loading"
                }
              }
            }
          }
        }
      }
    },
    "/api/predict/tissue-seg": {
      "post": {
        "tags": [
          "Prediction"
        ],
        "summary": "Tissue segmentation",
        "description": "Generate a tissue segmentation mask for a tile. Input: a 512×512 tile at 8.0 MPP. Output: a flattened binary mask (0/1) of length H×W in `output`.",
        "operationId": "predictTissueSeg",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModelRequest"
              },
              "example": {
                "image_data": "<base64-png-512x512>",
                "slide_name": "slide_A",
                "x": 0,
                "y": 0,
                "width": 512,
                "height": 512,
                "tissue_ratio": null,
                "patch_idx": 0,
                "resolution": 8
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mask result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (malformed or missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "500": {
            "description": "Inference error",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Internal server error"
                }
              }
            }
          },
          "503": {
            "description": "Model still loading or GPU unavailable",
            "content": {
              "application/json": {
                "example": {
                  "status": "loading"
                }
              }
            }
          }
        }
      }
    },
    "/api/metadata/m-optimus": {
      "get": {
        "tags": [
          "Prediction"
        ],
        "summary": "M-Optimus gene sets",
        "description": "Returns the ordered input and output gene sets used by the M-Optimus model.",
        "operationId": "metadataMOptimus",
        "responses": {
          "200": {
            "description": "Gene sets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "input_genes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Ordered Ensembl IDs expected in bulk_rna."
                    },
                    "output_genes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Ordered Ensembl IDs of predicted output genes."
                    }
                  }
                },
                "example": {
                  "input_genes": [
                    "ENSG00000000003",
                    "ENSG00000000005"
                  ],
                  "output_genes": [
                    "ENSG00000002330",
                    "ENSG00000003056"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/invocations": {
      "post": {
        "tags": [
          "Service"
        ],
        "summary": "SageMaker dispatch endpoint",
        "description": "SageMaker-compatible endpoint. Accepts a SageMakerRequest (ModelRequest plus `model_name` and `mode`) to route to the correct model and mode. Used by the SDK's AWS backend.",
        "operationId": "invocations",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SageMakerRequest"
              },
              "example": {
                "model_name": "h1",
                "mode": "embedding",
                "image_data": "<base64>",
                "slide_name": "slide_A",
                "x": 0,
                "y": 0,
                "width": 224,
                "height": 224,
                "patch_idx": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inference result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (malformed or missing fields)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "500": {
            "description": "Inference error",
            "content": {
              "application/json": {
                "example": {
                  "detail": "Internal server error"
                }
              }
            }
          },
          "503": {
            "description": "Model still loading or GPU unavailable",
            "content": {
              "application/json": {
                "example": {
                  "status": "loading"
                }
              }
            }
          }
        }
      }
    },
    "/ping": {
      "get": {
        "tags": [
          "Service"
        ],
        "summary": "Health check",
        "description": "Returns 200 with the loaded model list when the server is ready. Returns 503 with a status reason while loading or if the GPU/CUDA context is unavailable.",
        "operationId": "ping",
        "responses": {
          "200": {
            "description": "Healthy — all expected models loaded and GPU available",
            "content": {
              "application/json": {
                "example": {
                  "status": "ok",
                  "models": [
                    "h1",
                    "tissue-seg"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Loading or unhealthy. Reasons: still loading; partial_model_load; gpu_unavailable; cuda_context_corrupted.",
            "content": {
              "application/json": {
                "examples": {
                  "loading": {
                    "value": {
                      "status": "loading"
                    }
                  },
                  "partial_model_load": {
                    "value": {
                      "status": "unhealthy",
                      "reason": "partial_model_load",
                      "loaded": [
                        "tissue-seg"
                      ],
                      "expected": 2
                    }
                  },
                  "gpu_unavailable": {
                    "value": {
                      "status": "unhealthy",
                      "reason": "gpu_unavailable"
                    }
                  },
                  "cuda_context_corrupted": {
                    "value": {
                      "status": "unhealthy",
                      "reason": "cuda_context_corrupted"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/bioptimus/": {
      "get": {
        "tags": [
          "Service"
        ],
        "summary": "Service discovery",
        "description": "Returns links to the interactive docs and the OpenAPI schema.",
        "operationId": "serviceDiscovery",
        "responses": {
          "200": {
            "description": "Service info",
            "content": {
              "application/json": {
                "example": {
                  "service": "Bioptimus Model Server",
                  "docs": "/docs",
                  "openapi_schema": "/openapi.json"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ModelRequest": {
        "type": "object",
        "required": [
          "image_data",
          "slide_name",
          "x",
          "y",
          "width",
          "height",
          "patch_idx"
        ],
        "properties": {
          "image_data": {
            "type": "string",
            "description": "Base64-encoded PNG tile. (The SDK also accepts a PIL Image or raw bytes.)"
          },
          "bulk_rna": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "number"
            },
            "default": null,
            "description": "Optional bulk RNA counts vector (M-Optimus prediction only)."
          },
          "slide_name": {
            "type": "string",
            "description": "Stem of the source slide filename."
          },
          "x": {
            "type": "integer",
            "description": "Tile x-coordinate in the slide."
          },
          "y": {
            "type": "integer",
            "description": "Tile y-coordinate in the slide."
          },
          "width": {
            "type": "integer",
            "description": "Tile width in pixels."
          },
          "height": {
            "type": "integer",
            "description": "Tile height in pixels."
          },
          "tissue_ratio": {
            "type": [
              "number",
              "null"
            ],
            "default": null,
            "description": "Fraction of tissue in the tile."
          },
          "patch_idx": {
            "type": "integer",
            "description": "Index of this tile in the extraction plan."
          },
          "resolution": {
            "type": [
              "number",
              "null"
            ],
            "default": null,
            "description": "Extraction resolution in microns per pixel."
          }
        }
      },
      "ModelResponse": {
        "type": "object",
        "properties": {
          "output": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Model output vector: embeddings (1536), gene predictions, or a flattened mask."
          },
          "slide_name": {
            "type": "string"
          },
          "x": {
            "type": "integer"
          },
          "y": {
            "type": "integer"
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "tissue_ratio": {
            "type": [
              "number",
              "null"
            ]
          },
          "patch_idx": {
            "type": "integer"
          },
          "resolution": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "SageMakerRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ModelRequest"
          },
          {
            "type": "object",
            "required": [
              "model_name",
              "mode"
            ],
            "properties": {
              "model_name": {
                "type": "string",
                "enum": [
                  "h1",
                  "m-optimus",
                  "tissue-seg"
                ],
                "description": "Target model."
              },
              "mode": {
                "type": "string",
                "enum": [
                  "embedding",
                  "prediction",
                  "metadata"
                ],
                "description": "Dispatch mode."
              }
            }
          }
        ]
      },
      "ValidationError": {
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "properties": {
          "loc": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            }
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        }
      },
      "HTTPValidationError": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      }
    }
  },
  "security": []
}