{
  "openapi": "3.1.0",
  "info": {
    "title": "Lavandeira Living Apartments — API pública",
    "version": "1.2.0",
    "summary": "Apartamentos mobilados no centro histórico do Porto: Rua Chã 78-82 (estadias de 1 a 12 meses) e Rua dos Caldeireiros 180 (seis apartamentos prontos).",
    "description": "API JSON sem autenticação. Os endpoints de leitura devolvem a informação publicada no site sobre os doze apartamentos, a categoria de cada um (Standard, Superior, Premium) e os preços de lançamento por duração da estadia (no índice /api); o endpoint de escrita regista pedidos de informação, disponibilidade ou contacto, respondidos por email pela equipa. Todos os erros são JSON com código estável. Limites de pedidos anunciados nos cabeçalhos RateLimit.",
    "termsOfService": "https://lavandeira.nexiagent.com/privacidade",
    "contact": {
      "name": "Lavandeira Living Apartments",
      "url": "https://lavandeira.nexiagent.com/contacto"
    },
    "license": {
      "name": "Uso público da API para consulta e pedidos legítimos",
      "url": "https://lavandeira.nexiagent.com/developers"
    }
  },
  "externalDocs": {
    "description": "Documentação para developers e agentes",
    "url": "https://lavandeira.nexiagent.com/developers"
  },
  "servers": [
    {
      "url": "https://lavandeira.nexiagent.com",
      "description": "Produção"
    }
  ],
  "tags": [
    {
      "name": "apartamentos",
      "description": "Consulta dos apartamentos publicados"
    },
    {
      "name": "pedidos",
      "description": "Pedidos de informação, disponibilidade e contacto"
    },
    {
      "name": "meta",
      "description": "Descoberta da API"
    }
  ],
  "paths": {
    "/api": {
      "get": {
        "operationId": "apiIndex",
        "tags": [
          "meta"
        ],
        "summary": "Índice da API",
        "description": "Versão, ligações para a documentação e lista dos endpoints disponíveis.",
        "responses": {
          "200": {
            "description": "Índice",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          }
        }
      }
    },
    "/api/moradas": {
      "get": {
        "operationId": "listPlaces",
        "tags": [
          "apartamentos"
        ],
        "summary": "Listar as moradas",
        "description": "Devolve as duas moradas do Lavandeira Living Apartments: a Rua Chã 78-82 (doze apartamentos, ver /api/apartamentos) e a Rua dos Caldeireiros 180 (seis apartamentos prontos: 2 duplex T1 e 4 T0), com morada postal, unidades, estado, telefone, check-in e check-out.",
        "responses": {
          "200": {
            "description": "Lista de moradas",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceList"
                }
              }
            }
          },
          "429": {
            "description": "Limite de pedidos excedido",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/apartamentos": {
      "get": {
        "operationId": "listApartments",
        "tags": [
          "apartamentos"
        ],
        "summary": "Listar apartamentos",
        "description": "Devolve os doze apartamentos com tipologia, edifício, piso, orientação, áreas (m²), divisões, categoria e preço de lançamento por mês, descrição e imagens. Filtros opcionais por edifício, tipologia e categoria.",
        "parameters": [
          {
            "name": "building",
            "in": "query",
            "required": false,
            "description": "Filtrar por edifício",
            "schema": {
              "type": "string",
              "enum": [
                "rua-cha",
                "travessa"
              ]
            }
          },
          {
            "name": "typology",
            "in": "query",
            "required": false,
            "description": "Filtrar por tipologia",
            "schema": {
              "type": "string",
              "enum": [
                "T0",
                "T1"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filtrar por categoria de preço",
            "schema": {
              "type": "string",
              "enum": [
                "standard",
                "superior",
                "premium"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de apartamentos",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApartmentList"
                }
              }
            }
          },
          "400": {
            "description": "Filtro inválido",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de pedidos excedido (Retry-After indica quando repetir)",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/apartamentos/{slug}": {
      "get": {
        "operationId": "getApartment",
        "tags": [
          "apartamentos"
        ],
        "summary": "Obter um apartamento",
        "description": "Devolve um apartamento pelo slug (apartamento-b … apartamento-m).",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Identificador do apartamento, ex.: apartamento-c",
            "schema": {
              "type": "string",
              "pattern": "^apartamento-[a-m]$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Apartamento",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApartmentEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Apartamento inexistente",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de pedidos excedido",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/pedidos": {
      "post": {
        "operationId": "createRequest",
        "tags": [
          "pedidos"
        ],
        "summary": "Submeter um pedido",
        "description": "Regista um pedido de informações, de disponibilidade ou uma mensagem de contacto. O pedido é real: fica guardado e é respondido por email pela equipa. Sem autenticação. Limite: 6 pedidos por 10 minutos por IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestInput"
              },
              "examples": {
                "disponibilidade": {
                  "summary": "Pedido de disponibilidade",
                  "value": {
                    "tipo": "disponibilidade",
                    "nome": "Maria Silva",
                    "email": "maria@example.com",
                    "entrada": "2026-10-01",
                    "duracao": "9 meses",
                    "pessoas": 2,
                    "apartamento": "Apartamento L (T1, rés-do-chão, Travessa da Rua Chã)",
                    "mensagem": "Chegada em outubro; duas pessoas."
                  }
                },
                "informacoes": {
                  "summary": "Pedido de informações",
                  "value": {
                    "tipo": "informacoes",
                    "nome": "João Costa",
                    "email": "joao@example.com",
                    "interesse": "Marcar visita",
                    "mensagem": "Gostaria de visitar o edifício."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pedido registado",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestCreated"
                }
              }
            }
          },
          "400": {
            "description": "JSON inválido ou dados inválidos (o campo \"field\" indica qual)",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de pedidos excedido (Retry-After indica quando repetir)",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política em vigor (draft IETF ratelimit-headers), ex.: \"pedidos\";q=6;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "Estado atual: \"pedidos\";r=<restantes>;t=<segundos até reposição>",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Limite da janela (cabeçalho legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Pedidos restantes na janela (legado)",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela repor (legado)",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Place": {
        "type": "object",
        "description": "Uma morada do Lavandeira Living Apartments",
        "required": [
          "key",
          "name",
          "url",
          "address",
          "units",
          "status"
        ],
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "rua-cha",
              "caldeireiros"
            ]
          },
          "name": {
            "type": "string",
            "example": "Rua dos Caldeireiros 180"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "example": "Duplex T1"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "status": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "telephone": {
            "type": [
              "string",
              "null"
            ],
            "example": "+351910905093"
          },
          "checkin": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "from": {
                "type": "string",
                "example": "15:00"
              },
              "to": {
                "type": "string",
                "example": "24:00"
              }
            }
          },
          "checkout": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "until": {
                "type": "string",
                "example": "12:00"
              }
            }
          },
          "apartmentsEndpoint": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Endpoint com os apartamentos individuais, quando publicados"
          }
        }
      },
      "PlaceList": {
        "type": "object",
        "required": [
          "ok",
          "count",
          "places"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "count": {
            "type": "integer"
          },
          "places": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Place"
            }
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "required": [
          "name",
          "version",
          "docs",
          "openapi",
          "endpoints",
          "pricing",
          "places"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "llms": {
            "type": "string",
            "format": "uri"
          },
          "pricing": {
            "$ref": "#/components/schemas/Pricing"
          },
          "places": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Place"
            }
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "method": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "operationId": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Category": {
        "type": "object",
        "description": "Categoria do apartamento e intervalo de preço de lançamento por mês (EUR)",
        "required": [
          "key",
          "name",
          "monthlyPriceMin",
          "monthlyPriceMax",
          "currency"
        ],
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "standard",
              "superior",
              "premium"
            ]
          },
          "name": {
            "type": "string",
            "enum": [
              "Standard",
              "Superior",
              "Premium"
            ]
          },
          "monthlyPriceMin": {
            "type": "number",
            "example": 1295
          },
          "monthlyPriceMax": {
            "type": "number",
            "example": 1350
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "description": {
            "type": "string",
            "description": "Que apartamentos pertencem à categoria"
          }
        }
      },
      "DurationPrice": {
        "type": "object",
        "description": "Preço de lançamento por mês para um intervalo de duração da estadia",
        "required": [
          "label",
          "minMonths",
          "monthlyPrice"
        ],
        "properties": {
          "label": {
            "type": "string",
            "example": "2 a 3 meses"
          },
          "labelEn": {
            "type": "string",
            "example": "2 to 3 months"
          },
          "minMonths": {
            "type": "integer",
            "minimum": 1
          },
          "maxMonths": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null = sem limite superior"
          },
          "monthlyPrice": {
            "type": "number",
            "example": 1450
          }
        }
      },
      "Pricing": {
        "type": "object",
        "description": "Preços de lançamento: tabela por duração e categorias. O valor final de uma estadia depende da duração e da categoria e é confirmado por email.",
        "required": [
          "currency",
          "unit",
          "kind",
          "byDuration",
          "categories"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "unit": {
            "type": "string",
            "const": "month"
          },
          "kind": {
            "type": "string",
            "const": "launch",
            "description": "Preços de lançamento"
          },
          "byDuration": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DurationPrice"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Nearby": {
        "type": "object",
        "description": "Serviço perto do edifício",
        "required": [
          "name",
          "detail"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Lavandaria self-service"
          },
          "detail": {
            "type": "string",
            "example": "A 20 metros do edifício"
          },
          "distanceMeters": {
            "type": "integer",
            "example": 20
          }
        }
      },
      "Areas": {
        "type": "object",
        "description": "Áreas em metros quadrados (projeto de arquitetura)",
        "required": [
          "bruta",
          "varanda",
          "total"
        ],
        "properties": {
          "bruta": {
            "type": "number",
            "description": "Área bruta de construção (Abc); não é área útil"
          },
          "varanda": {
            "type": "number",
            "description": "Área da varanda (0 se não existir)"
          },
          "total": {
            "type": "number",
            "description": "Área total de construção (Atc = Abc + Abd)"
          }
        }
      },
      "Image": {
        "type": "object",
        "required": [
          "url",
          "alt",
          "kind"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "alt": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "fotografia",
              "visualizacao"
            ],
            "description": "Fotografia do estado atual ou visualização do projeto"
          }
        }
      },
      "Apartment": {
        "type": "object",
        "required": [
          "slug",
          "letter",
          "name",
          "typology",
          "building",
          "buildingName",
          "floor",
          "floorName",
          "orientation",
          "areas",
          "rooms",
          "description",
          "category",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "example": "apartamento-c"
          },
          "letter": {
            "type": "string",
            "example": "C"
          },
          "name": {
            "type": "string",
            "example": "Apartamento C"
          },
          "typology": {
            "type": "string",
            "enum": [
              "T0",
              "T1"
            ]
          },
          "building": {
            "type": "string",
            "enum": [
              "rua-cha",
              "travessa"
            ]
          },
          "buildingName": {
            "type": "string"
          },
          "floor": {
            "type": "integer",
            "description": "Piso (0 = rés-do-chão, -1 = nível do jardim)"
          },
          "floorName": {
            "type": "string"
          },
          "orientation": {
            "type": "string"
          },
          "areas": {
            "$ref": "#/components/schemas/Areas"
          },
          "rooms": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "description": {
            "type": "string"
          },
          "longDescription": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "included": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Equipamentos incluídos"
          },
          "nearby": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Nearby"
            }
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Página HTML"
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Image"
            }
          }
        }
      },
      "ApartmentList": {
        "type": "object",
        "required": [
          "ok",
          "count",
          "apartments"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "count": {
            "type": "integer"
          },
          "note": {
            "type": "string"
          },
          "apartments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Apartment"
            }
          }
        }
      },
      "ApartmentEnvelope": {
        "type": "object",
        "required": [
          "ok",
          "apartment"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "apartment": {
            "$ref": "#/components/schemas/Apartment"
          }
        }
      },
      "RequestInput": {
        "type": "object",
        "required": [
          "tipo",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "informacoes",
              "disponibilidade",
              "contacto"
            ],
            "description": "Tipo de pedido"
          },
          "nome": {
            "type": "string",
            "maxLength": 120
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200
          },
          "telefone": {
            "type": "string",
            "maxLength": 40
          },
          "interesse": {
            "type": "string",
            "maxLength": 80,
            "description": "Informações gerais · Marcar visita · Disponibilidade e condições · Um apartamento em particular"
          },
          "apartamento": {
            "type": "string",
            "maxLength": 80,
            "description": "Apartamento de interesse (texto livre, ex.: \"Apartamento C\")"
          },
          "mensagem": {
            "type": "string",
            "maxLength": 3000
          },
          "entrada": {
            "type": "string",
            "maxLength": 20,
            "description": "Data aproximada de entrada (AAAA-MM-DD ou texto)"
          },
          "duracao": {
            "type": "string",
            "maxLength": 40,
            "description": "1 mês · 2 a 3 meses · 4 a 6 meses · 7 a 11 meses · 12 meses ou mais · Outra duração (texto livre)"
          },
          "pessoas": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "origem": {
            "type": "string",
            "maxLength": 200,
            "description": "Página ou agente de origem"
          }
        }
      },
      "RequestCreated": {
        "type": "object",
        "required": [
          "ok",
          "id"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "emailed": {
            "type": "boolean",
            "description": "Se também foi enviado por email à equipa"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "error",
          "code",
          "status",
          "docs"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Mensagem legível"
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_json",
              "validation_error",
              "rate_limited",
              "not_found",
              "method_not_allowed",
              "not_acceptable",
              "internal_error"
            ]
          },
          "status": {
            "type": "integer"
          },
          "hint": {
            "type": "string",
            "description": "Como resolver"
          },
          "field": {
            "type": "string",
            "description": "Campo inválido"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}