{
  "openapi": "3.1.0",
  "info": {
    "title": "InmoControl API",
    "version": "1.0.0",
    "description": "API REST pública de InmoControl, software español de gestión de inversiones inmobiliarias. Permite consultar propiedades, leer y escribir movimientos contables y gestionar estancias de alquiler vacacional.",
    "contact": {
      "name": "InmoControl",
      "url": "https://inmocontrol.es/contacto"
    }
  },
  "servers": [
    {
      "url": "https://api.inmocontrol.es/v1",
      "description": "Producción"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Clave de API generada desde los ajustes de la cuenta, enviada como `Bearer <clave>`."
      }
    }
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Propiedades"
    },
    {
      "name": "Libro contable (ledger)"
    },
    {
      "name": "Estancias vacacionales (stays)"
    }
  ],
  "paths": {
    "/properties": {
      "get": {
        "summary": "Lista todas las propiedades activas del portfolio asociado a la API key",
        "description": "Lista todas las propiedades activas del portfolio asociado a la API key. Devuelve el ID, nombre, dirección, tipo de alquiler y moneda.",
        "tags": [
          "Propiedades"
        ],
        "operationId": "getProperties",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "550e8400-e29b-41d4-a716-446655440000",
                      "name": "Piso Centro Madrid",
                      "address": "Calle Gran Vía 1, Madrid",
                      "rental_type": "habitual",
                      "currency": "EUR"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "API key inválida o falta"
          },
          "403": {
            "description": "Falta el scope properties:read"
          }
        }
      }
    },
    "/properties/{propertyId}/ledger": {
      "get": {
        "summary": "Lista los movimientos (ingresos y gastos) del libro contable de una propiedad",
        "description": "Lista los movimientos (ingresos y gastos) del libro contable de una propiedad. Soporta paginación por cursor.",
        "tags": [
          "Libro contable (ledger)"
        ],
        "operationId": "getPropertiesPropertyIdLedger",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra movimientos desde esta fecha."
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra movimientos hasta esta fecha."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "'income' o 'expense'."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Número máximo por página. Por defecto 50."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor del valor next_cursor de la página anterior."
          }
        ],
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "uuid",
                      "date": "2026-04-15",
                      "type": "income",
                      "amount": 850,
                      "description": "Alquiler abril",
                      "category": "rent",
                      "is_deducible": false,
                      "has_official_invoice": false,
                      "payment_method": null,
                      "vendor": null,
                      "currency": "EUR",
                      "created_at": "2026-04-15T10:00:00Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Parámetro inválido (fecha o type)"
          },
          "401": {
            "description": "API key inválida"
          },
          "403": {
            "description": "Falta el scope ledger:read"
          },
          "404": {
            "description": "Propiedad no encontrada"
          }
        }
      },
      "post": {
        "summary": "Crea un nuevo movimiento (ingreso o gasto) en el libro contable de la propiedad",
        "description": "Crea un nuevo movimiento (ingreso o gasto) en el libro contable de la propiedad.",
        "tags": [
          "Libro contable (ledger)"
        ],
        "operationId": "postPropertiesPropertyIdLedger",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "date": "2026-04-15",
                "type": "income",
                "amount": 850,
                "description": "Alquiler abril",
                "category": "rent",
                "is_deducible": false,
                "has_official_invoice": false,
                "payment_method": "transferencia"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Datos inválidos (date, type, amount, description obligatorios)"
          },
          "403": {
            "description": "Falta el scope ledger:write"
          },
          "404": {
            "description": "Propiedad no encontrada"
          }
        }
      }
    },
    "/properties/{propertyId}/ledger/{entryId}": {
      "patch": {
        "summary": "Modifica campos concretos de un movimiento existente",
        "description": "Modifica campos concretos de un movimiento existente. Solo se actualizan los campos enviados en el body.",
        "tags": [
          "Libro contable (ledger)"
        ],
        "operationId": "patchPropertiesPropertyIdLedgerEntryId",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de entry."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "amount": 900,
                "description": "Alquiler abril (corregido)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Sin campos válidos o tipo incorrecto"
          },
          "404": {
            "description": "Movimiento o propiedad no encontrada"
          }
        }
      },
      "delete": {
        "summary": "Elimina un movimiento del libro contable",
        "description": "Elimina un movimiento del libro contable.",
        "tags": [
          "Libro contable (ledger)"
        ],
        "operationId": "deletePropertiesPropertyIdLedgerEntryId",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de entry."
          }
        ],
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": {
                    "id": "uuid",
                    "deleted": true
                  }
                }
              }
            }
          },
          "404": {
            "description": "Movimiento no encontrado"
          }
        }
      }
    },
    "/properties/{propertyId}/stays": {
      "get": {
        "summary": "Lista las estancias vacacionales de una propiedad",
        "description": "Lista las estancias vacacionales de una propiedad. Solo aplicable a propiedades con rental_type = 'vacacional'.",
        "tags": [
          "Estancias vacacionales (stays)"
        ],
        "operationId": "getPropertiesPropertyIdStays",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra por check_in desde esta fecha."
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra por check_in hasta esta fecha."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra por estado (confirmed, cancelled, etc.)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Número máximo por página. Por defecto 50."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Cursor de paginación."
          }
        ],
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "La propiedad no es vacacional"
          },
          "403": {
            "description": "Falta el scope stays:read"
          }
        }
      },
      "post": {
        "summary": "Crea una nueva estancia vacacional",
        "description": "Crea una nueva estancia vacacional. Si se proporciona platform_reservation_id, se hace deduplicación por (property_id, platform, platform_reservation_id) y devuelve 409 si ya existe.",
        "tags": [
          "Estancias vacacionales (stays)"
        ],
        "operationId": "postPropertiesPropertyIdStays",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Identificador de property."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "guest_name": "Juan Pérez",
                "check_in": "2026-05-10",
                "check_out": "2026-05-15",
                "platform": "airbnb",
                "platform_reservation_id": "HMABC123",
                "gross_amount": 500,
                "platform_commission": 75,
                "net_amount": 425,
                "cleaning_fee": 30,
                "guest_document": "12345678A",
                "guest_nationality": "ES",
                "status": "confirmed",
                "notes": "Cliente recurrente"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operación correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Datos inválidos o propiedad no vacacional"
          },
          "409": {
            "description": "Ya existe una estancia con ese platform_reservation_id"
          }
        }
      }
    }
  }
}
