{
 "openapi": "3.1.0",
 "info": {
  "title": "cst1 likes cameras",
  "version": "1.0.0",
  "summary": "A camera and lens guide that answers questions, for people and agents.",
  "description": "Answers are written from the guide's own pages (or computed, for focal-length conversions), cached, and free to call; model-written answers are rate-limited per network, and past a limit come back quoting the pages instead (outcome says which). The same tools are an MCP server at https://cst1likescameras.xyz/mcp."
 },
 "servers": [
  {
   "url": "https://cst1likescameras.xyz"
  }
 ],
 "externalDocs": {
  "description": "llms.txt",
  "url": "https://cst1likescameras.xyz/llms.txt"
 },
 "paths": {
  "/ask/{question_slug}": {
   "get": {
    "operationId": "askBySlug",
    "summary": "Ask a question written as words in the path",
    "description": "The answer as HTML by default, Markdown for Accept: text/markdown, or JSON for Accept: application/json. The slug is the question as 2 to 20 lowercase words of letters and digits joined by hyphens: /ask/which-camera-should-i-buy-for-travel.",
    "parameters": [
     {
      "name": "question_slug",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9]+(?:-[a-z0-9]+){1,19}$"
      },
      "example": "best-portrait-lens-for-the-sony-a6700"
     }
    ],
    "responses": {
     "200": {
      "description": "The answer",
      "content": {
       "text/html": {
        "schema": {
         "type": "string"
        }
       },
       "text/markdown": {
        "schema": {
         "type": "string"
        }
       },
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/RawAnswer"
        }
       }
      }
     },
     "404": {
      "description": "Not a valid slug",
      "content": {
       "text/plain": {
        "schema": {
         "type": "string"
        }
       }
      }
     }
    }
   }
  },
  "/api/ask": {
   "get": {
    "operationId": "ask",
    "summary": "Ask a question; get the answer with the gear it recommends, as JSON",
    "parameters": [
     {
      "name": "q",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "maxLength": 2000
      },
      "example": "best travel lens for the fujifilm x-t5"
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Answer"
        }
       }
      }
     },
     "400": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   },
   "post": {
    "operationId": "askPost",
    "summary": "Ask a question (JSON body); same answer as GET",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "question"
        ],
        "properties": {
         "question": {
          "type": "string",
          "maxLength": 2000
         }
        }
       }
      },
      "text/plain": {
       "schema": {
        "type": "string",
        "maxLength": 2000
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Answer"
        }
       }
      }
     },
     "400": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "413": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/convert": {
   "get": {
    "operationId": "convertFocalLength",
    "summary": "Focal-length and depth-of-field equivalents between sensor formats",
    "parameters": [
     {
      "name": "mm",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^\\d{1,4}(\\.\\d+)?(-\\d{1,4}(\\.\\d+)?)?$"
      },
      "description": "Focal length, or a zoom range like 18-55",
      "example": "50"
     },
     {
      "name": "from",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "medium",
        "full",
        "apsc",
        "apsc-canon",
        "mft",
        "1in",
        "1-1.3in",
        "1-1.7in",
        "1-2.3in"
       ]
      },
      "description": "The format the lens is used on. Without it, the answer is this focal length on every format, in `to` terms."
     },
     {
      "name": "to",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "medium",
        "full",
        "apsc",
        "apsc-canon",
        "mft",
        "1in",
        "1-1.3in",
        "1-1.7in",
        "1-2.3in"
       ]
      },
      "description": "The format to express it in (default full, or apsc when from=full)"
     },
     {
      "name": "f",
      "in": "query",
      "required": false,
      "schema": {
       "type": "number",
       "minimum": 0.7,
       "maximum": 64
      },
      "description": "f-number"
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Conversion"
        }
       }
      }
     },
     "400": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/gear/search": {
   "get": {
    "operationId": "searchGear",
    "summary": "Cameras and lenses whose names contain the words",
    "parameters": [
     {
      "name": "q",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "example": "fujifilm 56mm"
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SearchResults"
        }
       }
      }
     },
     "400": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/gear/camera/{id}": {
   "get": {
    "operationId": "getCamera",
    "summary": "One camera body",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The camera's catalogue id (the last part of its /gear/ page URL)",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Camera"
        }
       }
      }
     },
     "404": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/gear/camera/{id}/lenses": {
   "get": {
    "operationId": "lensesForCamera",
    "summary": "The lenses that fit a camera body",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The camera's catalogue id (the last part of its /gear/ page URL)",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
      }
     },
     {
      "name": "kind",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "prime",
        "zoom",
        "macro"
       ]
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/CameraLenses"
        }
       }
      }
     },
     "404": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "400": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/api/gear/lens/{id}": {
   "get": {
    "operationId": "getLens",
    "summary": "One lens",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The lens's catalogue id (the last part of its /gear/ page URL)",
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Lens"
        }
       }
      }
     },
     "404": {
      "description": "An error",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/mcp": {
   "post": {
    "operationId": "mcp",
    "summary": "MCP endpoint (Streamable HTTP, stateless, JSON responses only)",
    "description": "JSON-RPC 2.0, one message per POST. Protocol versions: 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26. Tools: ask_camera_guide, convert_focal_length, get_camera, get_lens, lenses_for_camera, search_gear.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "A JSON-RPC response",
      "content": {
       "application/json": {
        "schema": {
         "type": "object"
        }
       }
      }
     },
     "202": {
      "description": "A notification, accepted"
     },
     "400": {
      "description": "A JSON-RPC error"
     }
    }
   }
  }
 },
 "components": {
  "schemas": {
   "Error": {
    "type": "object",
    "properties": {
     "error": {
      "type": "string"
     },
     "status": {
      "type": "integer"
     }
    },
    "required": [
     "error"
    ]
   },
   "Source": {
    "type": "object",
    "properties": {
     "title": {
      "type": "string"
     },
     "url": {
      "type": "string",
      "format": "uri"
     }
    }
   },
   "RawAnswer": {
    "type": "object",
    "properties": {
     "question": {
      "type": "string"
     },
     "answer": {
      "type": "string",
      "description": "Markdown"
     },
     "sources": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Source"
      }
     },
     "outcome": {
      "type": "string"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "conversion": {
      "$ref": "#/components/schemas/Conversion"
     }
    }
   },
   "Answer": {
    "type": "object",
    "required": [
     "question",
     "answer_markdown",
     "outcome",
     "sources",
     "recommendations"
    ],
    "properties": {
     "question": {
      "type": "string"
     },
     "answer_markdown": {
      "type": "string",
      "description": "The answer; site links made absolute"
     },
     "outcome": {
      "type": "string",
      "description": "live (written now), cached, converted (computed), or a fallback that quotes the pages: no_model, rate_limited, busy, over_budget, model_error, no_match"
     },
     "sources": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Source"
      }
     },
     "recommendations": {
      "type": "array",
      "description": "Every camera and lens the answer links, in order",
      "items": {
       "oneOf": [
        {
         "$ref": "#/components/schemas/Camera"
        },
        {
         "$ref": "#/components/schemas/Lens"
        }
       ]
      }
     },
     "answer_url": {
      "type": [
       "string",
       "null"
      ],
      "format": "uri",
      "description": "The answer's /ask/ page, when the question fits in a slug"
     },
     "conversion": {
      "$ref": "#/components/schemas/Conversion"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     }
    }
   },
   "Camera": {
    "type": "object",
    "required": [
     "type",
     "id",
     "title",
     "url"
    ],
    "properties": {
     "type": {
      "const": "camera"
     },
     "id": {
      "type": "string"
     },
     "title": {
      "type": "string"
     },
     "url": {
      "type": "string",
      "format": "uri"
     },
     "brand": {
      "type": "string"
     },
     "mount": {
      "type": "string"
     },
     "mount_name": {
      "type": "string"
     },
     "sensor_format": {
      "type": "string",
      "enum": [
       "medium",
       "full",
       "apsc",
       "apsc-canon",
       "mft",
       "1in",
       "1-1.3in",
       "1-1.7in",
       "1-2.3in"
      ]
     },
     "sensor_format_name": {
      "type": "string"
     },
     "crop_factor": {
      "type": "number"
     },
     "megapixels": {
      "type": "number"
     },
     "stabilised": {
      "type": "boolean"
     },
     "year": {
      "type": "integer"
     },
     "kind": {
      "type": "string"
     },
     "built_in_lens": {
      "type": "object",
      "properties": {
       "focal_mm": {
        "type": "array",
        "items": {
         "type": "number"
        },
        "minItems": 2,
        "maxItems": 2
       },
       "max_aperture": {
        "type": "array",
        "items": {
         "type": "number"
        }
       },
       "focal_mm_full_frame": {
        "type": "array",
        "items": {
         "type": "number"
        },
        "minItems": 2,
        "maxItems": 2
       }
      }
     },
     "notes": {
      "type": "string"
     }
    }
   },
   "Lens": {
    "type": "object",
    "required": [
     "type",
     "id",
     "title",
     "url"
    ],
    "properties": {
     "type": {
      "const": "lens"
     },
     "id": {
      "type": "string"
     },
     "title": {
      "type": "string"
     },
     "url": {
      "type": "string",
      "format": "uri"
     },
     "brand": {
      "type": "string"
     },
     "mounts": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "covers": {
      "type": "string",
      "enum": [
       "medium",
       "full",
       "apsc",
       "apsc-canon",
       "mft",
       "1in",
       "1-1.3in",
       "1-1.7in",
       "1-2.3in"
      ]
     },
     "kind": {
      "type": "string",
      "enum": [
       "prime",
       "zoom",
       "macro"
      ]
     },
     "focal_mm": {
      "type": "array",
      "items": {
       "type": "number"
      },
      "minItems": 2,
      "maxItems": 2
     },
     "max_aperture": {
      "type": "array",
      "items": {
       "type": "number"
      }
     },
     "stabilised": {
      "type": "boolean"
     },
     "autofocus": {
      "type": "boolean"
     },
     "equivalents": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "format": {
         "type": "string",
         "enum": [
          "medium",
          "full",
          "apsc",
          "apsc-canon",
          "mft",
          "1in",
          "1-1.3in",
          "1-1.7in",
          "1-2.3in"
         ]
        },
        "focal_mm_full_frame": {
         "type": "array",
         "items": {
          "type": "number"
         },
         "minItems": 2,
         "maxItems": 2
        },
        "aperture_for_same_depth_of_field": {
         "type": "array",
         "items": {
          "type": "number"
         }
        }
       }
      }
     },
     "notes": {
      "type": "string"
     }
    }
   },
   "CameraLenses": {
    "type": "object",
    "properties": {
     "camera": {
      "$ref": "#/components/schemas/Camera"
     },
     "kind": {
      "type": [
       "string",
       "null"
      ]
     },
     "lenses": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Lens"
      }
     },
     "cropped_lenses": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Lens"
      }
     },
     "note": {
      "type": "string"
     }
    }
   },
   "Conversion": {
    "type": "object",
    "properties": {
     "focal_mm": {
      "type": "array",
      "items": {
       "type": "number"
      },
      "minItems": 2,
      "maxItems": 2
     },
     "aperture": {
      "type": [
       "number",
       "null"
      ]
     },
     "from": {
      "type": [
       "string",
       "null"
      ]
     },
     "to": {
      "type": "string"
     },
     "camera": {
      "type": [
       "string",
       "null"
      ]
     },
     "meaning": {
      "type": "string"
     },
     "markdown": {
      "type": "string"
     },
     "answer_url": {
      "type": [
       "string",
       "null"
      ],
      "format": "uri"
     },
     "rows": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "format": {
         "type": "string",
         "enum": [
          "medium",
          "full",
          "apsc",
          "apsc-canon",
          "mft",
          "1in",
          "1-1.3in",
          "1-1.7in",
          "1-2.3in"
         ]
        },
        "name": {
         "type": "string"
        },
        "crop": {
         "type": "number"
        },
        "focal_mm": {
         "type": "array",
         "items": {
          "type": "number"
         },
         "minItems": 2,
         "maxItems": 2
        },
        "aperture_for_same_depth_of_field": {
         "type": "number"
        }
       }
      }
     }
    }
   },
   "SearchResults": {
    "type": "object",
    "properties": {
     "query": {
      "type": "string"
     },
     "total": {
      "type": "integer"
     },
     "results": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "type": {
         "type": "string",
         "enum": [
          "camera",
          "lens"
         ]
        },
        "id": {
         "type": "string"
        },
        "title": {
         "type": "string"
        },
        "url": {
         "type": "string",
         "format": "uri"
        },
        "matched_words": {
         "type": "integer"
        }
       }
      }
     }
    }
   }
  }
 }
}