{
  "openapi": "3.1.0",
  "info": {
    "title": "Mashkof SQL API",
    "version": "1.2",
    "summary": "Read-only SQL over Israeli real-estate transactions (Israel Tax Authority, 1998–today) and enrichment data.",
    "description": "Run one read-only SQLite statement against the Mashkof database. Read https://mashkof.pov.sh/llms-full.txt for the schema, the statistics rules and example queries before querying. Data as of 2026-09-17; statistics windows end at 2026-06-30.",
    "contact": {
      "url": "https://mashkof.pov.sh/developers"
    }
  },
  "externalDocs": {
    "description": "Full reference for LLMs (Markdown)",
    "url": "https://mashkof.pov.sh/llms-full.txt"
  },
  "servers": [
    {
      "url": "https://mashkof.pov.sh"
    }
  ],
  "paths": {
    "/api/sql": {
      "get": {
        "operationId": "runSqlGet",
        "summary": "Run a read-only SQL query (GET)",
        "description": "One read-only statement (SELECT / WITH / VALUES / EXPLAIN). 10 s timeout, max 10000 rows. Cacheable for 300 s.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 20000
            },
            "description": "The SQL statement.",
            "example": "SELECT name, median_ppsqm_12m FROM settlements WHERE rank_ppsqm <= 5 ORDER BY rank_ppsqm"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "md"
              ],
              "default": "json"
            },
            "description": "Response format."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1000
            },
            "description": "Maximum rows to return."
          },
          {
            "name": "params",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Bound parameters as URL-encoded JSON: an array for ? placeholders or an object for :name placeholders."
          }
        ],
        "responses": {
          "200": {
            "description": "Query result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryResult"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid, non read-only or failing SQL (codes BAD_REQUEST, TOO_LONG, MULTIPLE_STATEMENTS, NOT_READ_ONLY, FORBIDDEN, SQL_ERROR).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "TIMEOUT — the query ran longer than 10 s.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED — slow down.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "BUSY / DB_UNAVAILABLE — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "runSql",
        "summary": "Run a read-only SQL query (POST)",
        "description": "Same as GET, with optional bound parameters. A text/plain body is taken as the SQL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QueryRequest"
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "maxLength": 20000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Query result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryResult"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid, non read-only or failing SQL (codes BAD_REQUEST, TOO_LONG, MULTIPLE_STATEMENTS, NOT_READ_ONLY, FORBIDDEN, SQL_ERROR).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "TIMEOUT — the query ran longer than 10 s.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED — slow down.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "BUSY / DB_UNAVAILABLE — retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/sql/schema": {
      "get": {
        "operationId": "getSchema",
        "summary": "Database schema with descriptions",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "md"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tables, columns (type, nullability, description, example), indexes, joins and query conventions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/sql/examples": {
      "get": {
        "operationId": "getExamples",
        "summary": "Curated example queries",
        "responses": {
          "200": {
            "description": "Example question → SQL pairs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Example"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "QueryRequest": {
        "type": "object",
        "required": [
          "sql"
        ],
        "properties": {
          "sql": {
            "type": "string",
            "maxLength": 20000,
            "description": "One read-only SQLite statement."
          },
          "params": {
            "description": "Bound parameters: array for ? placeholders, object for :name / @name / $name.",
            "oneOf": [
              {
                "type": "array",
                "items": {}
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "csv",
              "md"
            ],
            "default": "json"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "default": 1000
          }
        }
      },
      "QueryResult": {
        "type": "object",
        "required": [
          "columns",
          "rows",
          "row_count",
          "truncated",
          "limit",
          "elapsed_ms",
          "schema_version",
          "data_as_of"
        ],
        "properties": {
          "columns": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {}
            }
          },
          "row_count": {
            "type": "integer"
          },
          "truncated": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "elapsed_ms": {
            "type": "number"
          },
          "cached": {
            "type": "boolean"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "schema_version": {
            "type": "string"
          },
          "data_as_of": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "BAD_REQUEST",
                  "TOO_LONG",
                  "MULTIPLE_STATEMENTS",
                  "NOT_READ_ONLY",
                  "FORBIDDEN",
                  "SQL_ERROR",
                  "TIMEOUT",
                  "RATE_LIMITED",
                  "BUSY",
                  "DB_UNAVAILABLE",
                  "INTERNAL_ERROR"
                ]
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string"
              }
            }
          }
        }
      },
      "Example": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "title_en": {
            "type": "string"
          },
          "title_he": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "sql": {
            "type": "string"
          },
          "explanation": {
            "type": "string"
          },
          "tables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "verified": {
            "type": "object",
            "properties": {
              "rows": {
                "type": "integer"
              },
              "ms": {
                "type": "number"
              },
              "at": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}