{
  "openapi": "3.1.0",
  "info": {
    "title": "Subletka",
    "version": "1.0.0",
    "summary": "Rooms and flats for long-term stays in Warsaw and Kraków.",
    "description": "Read-only search over every active Subletka listing. No key needed. lang=pl returns prices in zł (PLN) and lang=en returns prices in € (EUR); max is in that currency. Each listing has a url on subletka.com where the person contacts the lister. At most 120 requests a minute per client. Send an X-Client-Id header, one value per end user or conversation, to get a separate budget. MCP clients get one through Mcp-Session-Id. Over a limit the server answers 429 with Retry-After.",
    "contact": {
      "name": "Subletka",
      "email": "support@subletka.com",
      "url": "https://subletka.com"
    }
  },
  "servers": [
    {
      "url": "https://subletka.com"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "Guide for AI agents",
    "url": "https://subletka.com/llms.txt"
  },
  "paths": {
    "/api/v1/listings": {
      "get": {
        "operationId": "searchListings",
        "summary": "Search sublets",
        "description": "Search active rooms and flats for long-term stays in Warsaw or Kraków by district, move-in and move-out dates, place type, rooms and maximum monthly rent. Each result links to its Subletka page, where the person can contact the lister.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": true,
            "description": "City slug. warszawa is Warsaw, krakow is Kraków.",
            "example": "warszawa",
            "schema": {
              "type": "string",
              "enum": [
                "warszawa",
                "krakow"
              ]
            }
          },
          {
            "name": "district",
            "in": "query",
            "required": false,
            "description": "District name. Accents, case and hyphens are optional, so mokotow, Mokotów and praga poludnie all work. GET /api/v1/cities lists every district.",
            "example": "mokotow",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 40
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Move-in date, YYYY-MM-DD. Returns places available on or before this day.",
            "example": "2026-11-01",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Move-out date, YYYY-MM-DD. Returns places available until at least this day or open-ended.",
            "example": "2027-01-31",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "format": "date"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Place types, comma separated in a URL. room is a private or shared room, studio is a one-room whole place, flat is any other whole place, whole is studio and flat together. Each result carries type room, studio or flat.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "room",
                  "studio",
                  "flat",
                  "whole"
                ]
              }
            },
            "style": "form",
            "explode": false
          },
          {
            "name": "rooms",
            "in": "query",
            "required": false,
            "description": "Minimum number of rooms in a whole place, as the ad counts them. Implies a whole place. Ads count differently. Some count bedrooms, others count every room the Polish way, where a studio is 1 room and a one-bedroom flat is 2. For a one-bedroom, use rooms=1 and say which results have type studio, or rooms=2 to rule studios out.",
            "example": 1,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10
            }
          },
          {
            "name": "max",
            "in": "query",
            "required": false,
            "description": "Maximum monthly rent in the currency of lang, so max=4000&lang=pl means 4000 zł and max=900&lang=en means €900.",
            "example": 4000,
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 1000000
            }
          },
          {
            "name": "furnished",
            "in": "query",
            "required": false,
            "description": "Only furnished places.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "pets",
            "in": "query",
            "required": false,
            "description": "Only places that allow pets.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Listings available by the move-in day (from, or today without from) come first, as on the website. Within each group, best fit first (value for money, area, transit time to the centre, listing quality and freshness, with at most two in a row from one building), cheapest monthly rent first, or newest first.",
            "schema": {
              "default": "best",
              "type": "string",
              "enum": [
                "best",
                "cheapest",
                "newest"
              ]
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Response language and currency. pl returns prices in zł (PLN), en returns prices in € (EUR). Use the currency the user's budget is in.",
            "schema": {
              "default": "en",
              "type": "string",
              "enum": [
                "pl",
                "en"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Results per page.",
            "schema": {
              "default": 20,
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Results to skip, for paging.",
            "schema": {
              "default": 0,
              "type": "integer",
              "minimum": 0,
              "maximum": 5000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query. issues names each bad parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/listings/{id}": {
      "get": {
        "operationId": "getListing",
        "summary": "Get a sublet",
        "description": "Full details of one listing, including its description, all photos and terms.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Listing id from a search result.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_:-]{1,64}$"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Response language and currency. pl returns prices in zł (PLN), en returns prices in € (EUR). Use the currency the user's budget is in.",
            "schema": {
              "default": "en",
              "type": "string",
              "enum": [
                "pl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Listing"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query. issues names each bad parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No active listing with this id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities": {
      "get": {
        "operationId": "listCities",
        "summary": "List cities and districts",
        "description": "Cities with active listing counts, lowest and median monthly rent, and every district name accepted by search_listings with its count and median monthly rent.",
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Response language and currency. pl returns prices in zł (PLN), en returns prices in € (EUR). Use the currency the user's budget is in.",
            "schema": {
              "default": "en",
              "type": "string",
              "enum": [
                "pl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitiesResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query. issues names each bad parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Retry after the Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Money": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "enum": [
              "PLN",
              "EUR"
            ]
          }
        },
        "required": [
          "amount",
          "currency"
        ],
        "additionalProperties": false
      },
      "Listing": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Subletka page for this listing. Send the person here; the main button reaches the lister."
          },
          "title": {
            "type": "string",
            "description": "Built from place type, rooms and district in the response language, so several results can share a title. Tell them apart by price, areaM2 and dates."
          },
          "city": {
            "type": "string",
            "enum": [
              "warszawa",
              "krakow"
            ]
          },
          "cityName": {
            "type": "string"
          },
          "district": {
            "type": [
              "string",
              "null"
            ]
          },
          "placeType": {
            "type": "string",
            "enum": [
              "whole_place",
              "private_room",
              "shared_room"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "room",
              "studio",
              "flat"
            ],
            "description": "Matches the type search parameter, where whole covers studio and flat. room is a private or shared room, studio is a one-room whole place, flat is any other whole place."
          },
          "rooms": {
            "description": "Rooms in a whole place as the lister counted them. Null for rooms in shared places.",
            "type": [
              "integer",
              "null"
            ]
          },
          "areaM2": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price": {
            "type": "object",
            "properties": {
              "amount": {
                "type": "integer"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "PLN",
                  "EUR"
                ]
              },
              "period": {
                "type": "string",
                "enum": [
                  "month",
                  "week",
                  "night"
                ]
              },
              "startingAt": {
                "type": "boolean",
                "description": "True when the amount is the lowest of several options. price.text then starts with from or od."
              },
              "billsIncluded": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "text": {
                "type": "string",
                "description": "Price as Subletka shows it, for example 2 400 zł/mies. or €560/mo."
              }
            },
            "required": [
              "amount",
              "currency",
              "period",
              "startingAt",
              "billsIncluded",
              "text"
            ],
            "additionalProperties": false
          },
          "monthlyRent": {
            "description": "Rent per month in the response currency, used by max and sort=cheapest.",
            "$ref": "#/components/schemas/Money"
          },
          "deposit": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "availableFrom": {
            "description": "YYYY-MM-DD. Null means available now.",
            "type": [
              "string",
              "null"
            ]
          },
          "availableTo": {
            "description": "YYYY-MM-DD. Null means open-ended.",
            "type": [
              "string",
              "null"
            ]
          },
          "minStayDays": {
            "description": "Shortest stay the lister accepts, in days. Null means not stated.",
            "type": [
              "integer",
              "null"
            ]
          },
          "furnished": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "petsAllowed": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "roommates": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "genders": {
                    "anyOf": [
                      {
                        "type": "string",
                        "enum": [
                          "female",
                          "male",
                          "mixed"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "preferredTenantGender": {
                    "anyOf": [
                      {
                        "type": "string",
                        "enum": [
                          "female",
                          "male"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "count",
                  "genders",
                  "preferredTenantGender"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "postedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "minutesToCentre": {
            "description": "Typical public transport time to the city centre on a weekday morning, walking included. Null when not known.",
            "type": [
              "integer",
              "null"
            ]
          },
          "description": {
            "description": "Lister's own text as written. Detail endpoint only.",
            "type": "string"
          },
          "descriptionLang": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "url",
          "title",
          "city",
          "cityName",
          "district",
          "placeType",
          "type",
          "rooms",
          "areaM2",
          "price",
          "monthlyRent",
          "deposit",
          "availableFrom",
          "availableTo",
          "minStayDays",
          "furnished",
          "petsAllowed",
          "roommates",
          "photos",
          "postedAt",
          "minutesToCentre"
        ],
        "additionalProperties": false
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "query": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "The query as understood, with the district name normalised."
          },
          "currency": {
            "type": "string",
            "enum": [
              "PLN",
              "EUR"
            ]
          },
          "total": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "next": {
            "anyOf": [
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "description": "Next page of results, or null."
          },
          "searchPage": {
            "type": "string",
            "format": "uri",
            "description": "The same search on the Subletka website, for people: the district page when a district is given, otherwise the city page. The website has no rooms filter, so this page leaves rooms out."
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Listing"
            }
          }
        },
        "required": [
          "query",
          "currency",
          "total",
          "count",
          "next",
          "searchPage",
          "results"
        ],
        "additionalProperties": false
      },
      "CitiesResult": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "PLN",
              "EUR"
            ]
          },
          "cities": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "city": {
                  "type": "string",
                  "enum": [
                    "warszawa",
                    "krakow"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "search": {
                  "type": "string",
                  "format": "uri"
                },
                "activeListings": {
                  "type": "integer"
                },
                "lowestMonthlyRent": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/Money"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "medianMonthlyRent": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/Money"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Median monthly rent of active listings in the city."
                },
                "districts": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "activeListings": {
                        "type": "integer"
                      },
                      "medianMonthlyRent": {
                        "anyOf": [
                          {
                            "$ref": "#/components/schemas/Money"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "Median monthly rent of active listings in the district."
                      }
                    },
                    "required": [
                      "name",
                      "activeListings",
                      "medianMonthlyRent"
                    ],
                    "additionalProperties": false
                  }
                }
              },
              "required": [
                "city",
                "name",
                "url",
                "search",
                "activeListings",
                "lowestMonthlyRent",
                "medianMonthlyRent",
                "districts"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "currency",
          "cities"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_query",
              "not_found",
              "rate_limited"
            ]
          },
          "message": {
            "type": "string"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "param": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "param",
                "message"
              ],
              "additionalProperties": false
            }
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      }
    }
  }
}