{
 "openapi": "3.1.0",
 "info": {
  "title": "FirmTape LAB API",
  "version": "1.0.0",
  "description": "Run LAB from a script: describe an idea, backtest it, quant optimize it and read the out-of-sample verdict, on the same conveyor and the same credits as the terminal. The same key runs the OPTIONS strategy tester over the archive. Neither gives trading signals."
 },
 "servers": [
  {
   "url": "https://firmtape.com"
  }
 ],
 "security": [
  {
   "bearer": []
  }
 ],
 "x-product": "lab",
 "x-features": [
  "lab-api",
  "backtester",
  "quant-optimize",
  "credits-and-tokens"
 ],
 "components": {
  "securitySchemes": {
   "bearer": {
    "type": "http",
    "scheme": "bearer",
    "description": "An API key with the `lab` scope (`labk_...`), made on Account, LAB."
   }
  },
  "schemas": {
   "Error": {
    "type": "object",
    "required": [
     "error"
    ],
    "properties": {
     "error": {
      "type": "string",
      "description": "What happened, in words."
     },
     "buy": {
      "type": "string",
      "description": "With 402: where to buy credits."
     },
     "holder": {
      "type": "string",
      "description": "With 409: the idea holding the conveyor."
     },
     "refunded": {
      "type": "integer",
      "description": "Credits refunded."
     }
    }
   }
  }
 },
 "paths": {
  "/api/lab/chat": {
   "post": {
    "summary": "Develop or amend an idea",
    "description": "Compile a plain-words idea into a rule, or amend an existing one by `id`. Free, but needs a positive balance. Any chat on an idea sends it back to the start of the conveyor.",
    "operationId": "labChat",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "message"
        ],
        "properties": {
         "message": {
          "type": "string",
          "description": "Plain-English research idea",
          "example": "Test a 15-minute mean reversion after a three-sigma SPX move above VWAP."
         },
         "id": {
          "type": "string",
          "description": "Existing LAB idea id to amend"
         }
        }
       }
      }
     }
    },
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "Credits; one idea on the conveyor at a time",
    "x-slug": "chat",
    "x-features": [
     "lab-api",
     "backtester"
    ],
    "x-since": "2026-08-12",
    "responses": {
     "200": {
      "description": "`kind: \"spec\"` with the compiled idea, or `kind: \"chat\"` with a question.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "kind": {
           "type": "string",
           "description": "`spec`, `chat` or `error`.",
           "enum": [
            "spec",
            "chat",
            "error"
           ]
          },
          "say": {
           "type": "string",
           "description": "LAB's answer in words."
          },
          "idea": {
           "type": "object",
           "description": "With `spec`: `{ id, name, spec }`."
          },
          "errors": {
           "type": "array",
           "items": {},
           "description": "With `error`: what could not compile."
          }
         }
        },
        "example": {
         "kind": "spec",
         "say": "Compiled: fade a +3 sigma stretch above vwap.",
         "idea": {
          "id": "i_abc123def456",
          "name": "vwap stretch fade",
          "spec": {}
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is wrong.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "No credits.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "No such idea or job.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "The rule could not be compiled or was refused (refunded).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/lab/run": {
   "post": {
    "summary": "Backtest or optimize an idea",
    "description": "Run the idea's current conveyor step: `backtest` first (rule to tested), then `quant` (tested to ready). One credit, refunded on failure.",
    "operationId": "labRun",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "id",
         "kind"
        ],
        "properties": {
         "id": {
          "type": "string",
          "description": "Compiled LAB idea id"
         },
         "kind": {
          "type": "string",
          "enum": [
           "backtest",
           "quant"
          ],
          "description": "Run backtest first, then quant optimization"
         }
        }
       }
      }
     }
    },
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "1 credit a run",
    "x-slug": "run",
    "x-features": [
     "lab-api",
     "backtester",
     "quant-optimize"
    ],
    "x-since": "2026-08-12",
    "responses": {
     "200": {
      "description": "`{ idea, result, state }`: the engine's verdict per leg and the balance after.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "idea": {
           "type": "object",
           "description": "The idea after the step."
          },
          "result": {
           "type": "object",
           "description": "Per leg: era tables (all, this year, holdout), the full rule against its baseline, and `verdict`."
          },
          "state": {
           "type": "object",
           "description": "Balance and ideas."
          }
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is wrong.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "No credits.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "No such idea or job.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Another idea holds the conveyor, or a step came out of order.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "422": {
      "description": "The rule could not be compiled or was refused (refunded).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "502": {
      "description": "The engine failed (refunded); retry.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/lab/state": {
   "get": {
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "Credits; one idea on the conveyor at a time",
    "operationId": "labState",
    "x-slug": "state",
    "summary": "Ideas, conveyor and balance",
    "description": "Everything in one call: the account's ideas with their stages and results, the idea holding the conveyor, and the credit balance. Free.",
    "x-features": [
     "lab-api",
     "credits-and-tokens"
    ],
    "x-since": "2026-08-12",
    "responses": {
     "200": {
      "description": "The state.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ideas": {
           "type": "array",
           "items": {},
           "description": "`{ id, name, stage, spec, results }`."
          },
          "conveyor": {
           "type": [
            "string",
            "null"
           ],
           "description": "The idea holding the conveyor."
          },
          "balance": {
           "type": "object",
           "description": "`{ monthly, purchased, total }` credits."
          }
         }
        },
        "example": {
         "ideas": [],
         "conveyor": null,
         "balance": {
          "monthly": 5,
          "purchased": 0,
          "total": 5
         }
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/lab/thread": {
   "get": {
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "Credits; one idea on the conveyor at a time",
    "operationId": "labThread",
    "x-slug": "thread",
    "summary": "An idea's chat thread",
    "description": "The chat thread of one idea. Free.",
    "x-features": [
     "lab-api"
    ],
    "x-since": "2026-08-12",
    "parameters": [
     {
      "name": "id",
      "in": "query",
      "required": true,
      "description": "The idea, `i_...`.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The thread.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "No such idea or job.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/lab/idea": {
   "post": {
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "Credits; one idea on the conveyor at a time",
    "operationId": "labIdea",
    "x-slug": "idea",
    "summary": "Desk actions on an idea",
    "description": "`desk` (a ready idea onto the desk, freeing the conveyor), `drop` (back to the start, freeing the conveyor), `delete`, or `set` (the desk's switches: `on`, `draw`, `alert`). Free.",
    "x-features": [
     "lab-api",
     "desk",
     "desk-alerts"
    ],
    "x-since": "2026-08-12",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "id",
         "action"
        ],
        "properties": {
         "id": {
          "type": "string",
          "description": "The idea."
         },
         "action": {
          "type": "string",
          "description": "`desk`, `drop`, `delete` or `set`.",
          "enum": [
           "desk",
           "drop",
           "delete",
           "set"
          ]
         },
         "on": {
          "type": "boolean",
          "description": "With `set`."
         },
         "draw": {
          "type": "string",
          "description": "With `set`."
         },
         "alert": {
          "type": "string",
          "description": "With `set`."
         }
        }
       },
       "example": {
        "id": "i_abc123def456",
        "action": "desk"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The idea after the action.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "400": {
      "description": "A parameter is wrong.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "No such idea or job.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "Another idea holds the conveyor, or a step came out of order.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/lab/vocabulary": {
   "get": {
    "operationId": "labVocabulary",
    "x-slug": "vocabulary",
    "summary": "The LAB vocabulary",
    "security": [],
    "x-plan": "replay",
    "x-key": "none",
    "description": "Every column a rule may name, with its unit, range and an example: the compiler's own dictionary. Public, no key; the page for a reader is [/lab/vocabulary](/lab/vocabulary).",
    "x-features": [
     "vocabulary"
    ],
    "x-since": "2026-09-04",
    "responses": {
     "200": {
      "description": "The vocabulary.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     }
    }
   }
  },
  "/api/struct/run": {
   "post": {
    "summary": "Test an options structure over the archive",
    "description": "Price an SPX 0DTE structure of up to four legs at an entry minute over the archived sessions on real quotes and settlement. 10 credits, refunded on failure; answers 202 with a job to poll.",
    "operationId": "structRun",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "spec"
        ],
        "properties": {
         "spec": {
          "type": "object",
          "required": [
           "legs",
           "entry"
          ],
          "properties": {
           "legs": {
            "type": "array",
            "description": "One to four SPX 0DTE legs, as the terminal's OPTIONS pane builds them",
            "items": {
             "type": "object"
            }
           },
           "entry": {
            "type": "string",
            "description": "Entry time, HH:MM ET",
            "example": "10:00"
           }
          }
         }
        }
       }
      }
     }
    },
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "10 credits a run",
    "x-slug": "options-run",
    "x-features": [
     "archive-run",
     "structure-tester"
    ],
    "x-since": "2026-08-31",
    "responses": {
     "202": {
      "description": "`{ job }`: poll `GET /api/struct/job`.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "job": {
           "type": "string",
           "description": "The job id."
          }
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is wrong.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No key, or an unknown or revoked one.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "No credits.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/struct/job": {
   "get": {
    "summary": "Poll an options run",
    "operationId": "structJob",
    "parameters": [
     {
      "name": "id",
      "in": "query",
      "required": true,
      "description": "The job named by /api/struct/run",
      "schema": {
       "type": "string"
      }
     }
    ],
    "x-plan": "lab",
    "x-key": "lab",
    "x-limit": "Credits; one idea on the conveyor at a time",
    "x-slug": "options-job",
    "description": "A structure run's state: running, done with its result, or error with the credits refunded.",
    "x-features": [
     "archive-run"
    ],
    "x-since": "2026-08-31",
    "responses": {
     "200": {
      "description": "`{ state, result? , error?, refunded? }`.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "state": {
           "type": "string",
           "description": "`running`, `done` or `error`."
          }
         }
        }
       }
      }
     },
     "404": {
      "description": "No such idea or job.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  }
 }
}