{
  "openapi": "3.1.0",
  "info": {
    "title": "TMInter Online public API",
    "version": "1.0.0",
    "description": "Residue-level interaction propensity for alpha-helical transmembrane proteins. Public API calls require no account or API key. Exact normalized sequences reuse frozen TMAtlas predictions or an existing job for the active inference version. CPU inference is asynchronous and may be slow. Store the job ID and poll every five seconds or less often; client timeouts do not cancel server work. Sequences and results are stored in a shared cache and can be retrieved by anyone with the job ID. One protein per request; no batch or cancellation endpoint."
  },
  "servers": [
    {
      "url": "https://tminter.online.sunnylab.org"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "Python client and API guide",
    "url": "https://tminter.online.sunnylab.org/api/"
  },
  "paths": {
    "/api/v1/info": {
      "get": {
        "operationId": "getInfo",
        "summary": "Inspect the active method and limits",
        "responses": {
          "200": {
            "description": "Service configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Info"
                }
              }
            }
          },
          "503": {
            "description": "The inference method is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The service could not process the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs": {
      "post": {
        "operationId": "submitJob",
        "summary": "Submit one protein sequence or FASTA record",
        "description": "Checks the configured Atlas release first, then jobs for the active fresh-inference version. Removes an initial FASTA header and all whitespace, and uppercases letters. Fresh inference accepts at most 2,174 residues; sequences up to 15,000 residues require an exact configured Atlas cache hit. Repeated normalized sequences reuse pending, completed or failed jobs within a version; a failed job requires an explicit retry. A submission body may contain at most 400,000 characters.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Submission"
              },
              "example": {
                "sequence": "MALWMRLLPLLALLALWGPDPAAA"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing job or immediate Atlas result. Inspect status; an existing job can be in any state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "202": {
            "description": "New inference work accepted and queued. Save the job ID and poll its status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, invalid sequence, multiple FASTA records, or an unsupported sequence length.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 400,000 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Queue is full or the inference method is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The service could not process the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Saved job ID returned by submission.",
          "schema": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9-]+$"
          }
        }
      ],
      "get": {
        "operationId": "getJob",
        "summary": "Retrieve job status and available result",
        "description": "Returns queued, running, complete or failed. Complete jobs contain probabilities and provenance. Failed jobs contain an error. Retrieving a job does not require authentication.",
        "responses": {
          "200": {
            "description": "Job record. Poll every five seconds or less often while queued or running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "description": "Job not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The service could not process the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Saved job ID returned by submission.",
          "schema": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9-]+$"
          }
        }
      ],
      "post": {
        "operationId": "retryJob",
        "summary": "Retry a failed job of the active method",
        "description": "Requires no request body. Keeps the same job ID, changes status to queued and clears the previous error. For an inactive model version, submit the sequence again instead.",
        "responses": {
          "202": {
            "description": "Job queued again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "description": "Job not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Job is not failed, is already being retried, or its model version is no longer active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Queue is full or the inference method is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The service could not process the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}/download.tsv": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Saved job ID returned by submission.",
          "schema": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9-]+$"
          }
        }
      ],
      "get": {
        "operationId": "downloadTsv",
        "summary": "Download completed residue predictions",
        "description": "UTF-8 tab-separated text with one header row and one row per residue. Columns: job_id, model_version, source, inference_scope, threshold, position (1-based), aa, probability, predicted_label. The predicted label is 1 when probability >= threshold, otherwise 0. Retrieve the job JSON to preserve the full provenance object and Atlas metadata.",
        "responses": {
          "200": {
            "description": "Completed predictions with per-row model and scope metadata.",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment with filename TMInter_<job_id>.tsv.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/tab-separated-values": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Job not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Predictions are available only when the job is complete.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The service could not process the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Submission": {
        "type": "object",
        "required": [
          "sequence"
        ],
        "properties": {
          "sequence": {
            "type": "string",
            "description": "Protein sequence or a single FASTA record. Normalized letters must be ACDEFGHIKLMNPQRSTVWYBXZUO. No gaps or stop symbols. Maximum normalized length is 15,000; cache misses are limited to 2,174."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Info": {
        "type": "object",
        "required": [
          "model_version",
          "atlas_release_id",
          "atlas_model_version",
          "threshold",
          "max_inference_length",
          "max_sequence_length",
          "queue_capacity",
          "inference_scope",
          "alphabet"
        ],
        "properties": {
          "model_version": {
            "type": "string",
            "description": "Active fresh-inference method version."
          },
          "atlas_release_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "atlas_model_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "atlas:<release_id>, or null when no Atlas release is configured."
          },
          "threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Fresh-inference decision threshold. Atlas results use their own reported threshold."
          },
          "max_inference_length": {
            "type": "integer",
            "const": 2174
          },
          "max_sequence_length": {
            "type": "integer",
            "const": 15000
          },
          "queue_capacity": {
            "type": "integer",
            "const": 20
          },
          "inference_scope": {
            "type": "string",
            "const": "exact"
          },
          "alphabet": {
            "type": "string",
            "const": "ACDEFGHIKLMNPQRSTVWYBXZUO"
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "status",
          "sequence",
          "length",
          "source",
          "model_version",
          "threshold",
          "inference_scope",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Durable job identifier."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "complete",
              "failed"
            ]
          },
          "sequence": {
            "type": "string",
            "pattern": "^[ACDEFGHIKLMNPQRSTVWYBXZUO]+$",
            "minLength": 1,
            "maxLength": 15000,
            "description": "Normalized uppercase sequence; whitespace and FASTA header have been removed."
          },
          "length": {
            "type": "integer",
            "minimum": 1,
            "maximum": 15000
          },
          "source": {
            "type": "string",
            "enum": [
              "atlas",
              "inference"
            ]
          },
          "model_version": {
            "type": "string",
            "description": "Fresh inference version or atlas:<release_id>. Use this result value when recording provenance."
          },
          "threshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Predicted label is 1 when probability >= threshold."
          },
          "inference_scope": {
            "type": "string",
            "enum": [
              "exact",
              "windowed",
              "windowed_length_ood"
            ],
            "description": "Fresh inference is exact. Atlas predictions retain their frozen scope."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "error": {
            "type": "string",
            "description": "Present for a failed job."
          },
          "probabilities": {
            "type": "array",
            "minItems": 1,
            "maxItems": 15000,
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "description": "Present for completed jobs. One value per residue in sequence order; array length equals length."
          },
          "provenance": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present for completed jobs. Scientific provenance fields vary by source; preserve the full object."
          },
          "atlas_accession": {
            "type": "string",
            "description": "Representative accession for an Atlas match, when available."
          },
          "atlas_release_id": {
            "type": "string",
            "description": "Frozen release identifier for an Atlas match."
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "status": {
                  "const": "complete"
                }
              }
            },
            "then": {
              "required": [
                "probabilities",
                "provenance"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "status": {
                  "const": "failed"
                }
              }
            },
            "then": {
              "required": [
                "error"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "source": {
                  "const": "atlas"
                }
              }
            },
            "then": {
              "required": [
                "atlas_release_id"
              ]
            }
          }
        ]
      }
    }
  }
}
