{
  "openapi": "3.1.0",
  "info": {
    "title": "Hella Partner API",
    "version": "1.0.0-beta.1",
    "summary": "Versioned, provider-neutral property discovery, availability and quote contract.",
    "description": "The Hella Partner API lets approved partners discover properties, check availability,\nrequest an indicative quote and hand the traveller off to Hella.\n\nEvery operation requires a server-side partner credential. Browser CORS policy is not\nan authentication mechanism. Dates are calendar dates in the property's IANA timezone;\na stay uses the half-open interval `[checkIn, checkOut)`, so check-out is not an occupied night.\nMonetary values are decimal strings in the response currency, never floating-point numbers.\n",
    "contact": {
      "name": "Hella partner integration support",
      "url": "https://www.hellastays.com/help#contact"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Hella-Proprietary"
    },
    "x-api-status": "beta",
    "x-last-updated": "2026-07-13",
    "x-changelog": [
      {
        "version": "1.0.0-beta.1",
        "date": "2026-07-13",
        "changes": [
          "Introduced the provider-neutral Partner API v1 contract.",
          "Standardised validation errors on RFC 9457 Problem Details.",
          "Added server-generated handoff URLs with signed partner attribution."
        ]
      }
    ]
  },
  "servers": [
    {
      "url": "https://www.app.hellastays.com/app/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "PartnerBearer": []
    }
  ],
  "x-max-stay-nights": 60,
  "tags": [
    {
      "name": "Properties",
      "description": "Provider-neutral property catalogue and availability."
    },
    {
      "name": "Quotes",
      "description": "Indicative pricing and secure Hella handoff."
    }
  ],
  "paths": {
    "/v1/properties": {
      "get": {
        "operationId": "listPartnerProperties",
        "summary": "Search partner-visible properties",
        "description": "Returns only partner-safe catalogue fields. Exact addresses and coordinates are not exposed.\nWithout dates this operation is catalogue discovery. `checkIn` and `checkOut` must either\nboth be omitted or both be supplied. With dates, `items` contains only properties available\nfor `[checkIn, checkOut)` in each property's timezone and able to host `guests` using one\neligible inventory type for the whole stay. Stays may contain at most 60 nights. Results are\nordered by opaque `propertyId` before pagination. Unknown query parameters are rejected with\n`VALIDATION_ERROR`.\n",
        "tags": [
          "Properties"
        ],
        "x-idempotency": "idempotent",
        "x-cache-control": "private, max-age=60",
        "x-required-scopes": [
          "properties:read"
        ],
        "x-max-stay-nights": 60,
        "x-pagination-order": [
          "propertyId"
        ],
        "x-error-codes": {
          "400": [
            "VALIDATION_ERROR"
          ],
          "401": [
            "PARTNER_AUTH_REQUIRED",
            "INVALID_PARTNER_CREDENTIAL",
            "PARTNER_ACCESS_REVOKED"
          ],
          "403": [
            "PARTNER_SCOPE_INSUFFICIENT",
            "ORIGIN_NOT_ALLOWED"
          ],
          "429": [
            "RATE_LIMIT_EXCEEDED"
          ],
          "500": [
            "INTERNAL_ERROR"
          ],
          "503": [
            "PARTNER_API_UNAVAILABLE"
          ]
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/City"
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "$ref": "#/components/parameters/CheckInOptional"
          },
          {
            "$ref": "#/components/parameters/CheckOutOptional"
          },
          {
            "$ref": "#/components/parameters/Guests"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "$ref": "#/components/parameters/Locale"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of matching partner-safe properties.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPropertyList"
                },
                "examples": {
                  "roma": {
                    "$ref": "#/components/examples/PropertyListExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationProblem"
          },
          "403": {
            "$ref": "#/components/responses/AuthorisationProblem"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitProblem"
          },
          "500": {
            "$ref": "#/components/responses/InternalProblem"
          },
          "503": {
            "$ref": "#/components/responses/PartnerApiUnavailableProblem"
          }
        }
      }
    },
    "/v1/properties/{propertyId}": {
      "get": {
        "operationId": "getPartnerProperty",
        "summary": "Get a property",
        "description": "Returns the partner-safe detail for one opaque property identifier. Unknown query\nparameters are rejected with `VALIDATION_ERROR`.\n",
        "tags": [
          "Properties"
        ],
        "x-idempotency": "idempotent",
        "x-cache-control": "private, max-age=60",
        "x-required-scopes": [
          "properties:read"
        ],
        "x-error-codes": {
          "400": [
            "VALIDATION_ERROR"
          ],
          "401": [
            "PARTNER_AUTH_REQUIRED",
            "INVALID_PARTNER_CREDENTIAL",
            "PARTNER_ACCESS_REVOKED"
          ],
          "403": [
            "PARTNER_SCOPE_INSUFFICIENT",
            "ORIGIN_NOT_ALLOWED"
          ],
          "404": [
            "PROPERTY_NOT_FOUND"
          ],
          "429": [
            "RATE_LIMIT_EXCEEDED"
          ],
          "500": [
            "INTERNAL_ERROR"
          ],
          "503": [
            "PARTNER_API_UNAVAILABLE"
          ]
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/PropertyId"
          },
          {
            "$ref": "#/components/parameters/Locale"
          }
        ],
        "responses": {
          "200": {
            "description": "Partner-safe property detail.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPropertyDetail"
                },
                "examples": {
                  "property": {
                    "$ref": "#/components/examples/PropertyDetailExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationProblem"
          },
          "403": {
            "$ref": "#/components/responses/AuthorisationProblem"
          },
          "404": {
            "$ref": "#/components/responses/PropertyNotFoundProblem"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitProblem"
          },
          "500": {
            "$ref": "#/components/responses/InternalProblem"
          },
          "503": {
            "$ref": "#/components/responses/PartnerApiUnavailableProblem"
          }
        }
      }
    },
    "/v1/properties/{propertyId}/availability": {
      "get": {
        "operationId": "getPartnerPropertyAvailability",
        "summary": "Check property availability",
        "description": "Checks every occupied local calendar night in `[checkIn, checkOut)` using the property's\nIANA timezone; check-out is exclusive. `available: true` guarantees that one eligible\ninventory type can host the requested guests for the entire interval, without combining\ncapacity from different inventory types across nights. Stays may contain at most 60 nights.\nAn unknown property returns 404, never an empty successful response. Unknown query parameters\nare rejected with `VALIDATION_ERROR`.\n",
        "tags": [
          "Properties"
        ],
        "x-idempotency": "idempotent",
        "x-cache-control": "no-store",
        "x-required-scopes": [
          "availability:read"
        ],
        "x-max-stay-nights": 60,
        "x-error-codes": {
          "400": [
            "VALIDATION_ERROR"
          ],
          "401": [
            "PARTNER_AUTH_REQUIRED",
            "INVALID_PARTNER_CREDENTIAL",
            "PARTNER_ACCESS_REVOKED"
          ],
          "403": [
            "PARTNER_SCOPE_INSUFFICIENT",
            "ORIGIN_NOT_ALLOWED"
          ],
          "404": [
            "PROPERTY_NOT_FOUND"
          ],
          "429": [
            "RATE_LIMIT_EXCEEDED"
          ],
          "500": [
            "INTERNAL_ERROR"
          ],
          "503": [
            "PARTNER_API_UNAVAILABLE"
          ]
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/PropertyId"
          },
          {
            "$ref": "#/components/parameters/CheckIn"
          },
          {
            "$ref": "#/components/parameters/CheckOut"
          },
          {
            "$ref": "#/components/parameters/Guests"
          }
        ],
        "responses": {
          "200": {
            "description": "Availability for the requested stay.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAvailability"
                },
                "examples": {
                  "available": {
                    "$ref": "#/components/examples/AvailabilityExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationProblem"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationProblem"
          },
          "403": {
            "$ref": "#/components/responses/AuthorisationProblem"
          },
          "404": {
            "$ref": "#/components/responses/PropertyNotFoundProblem"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitProblem"
          },
          "500": {
            "$ref": "#/components/responses/InternalProblem"
          },
          "503": {
            "$ref": "#/components/responses/PartnerApiUnavailableProblem"
          }
        }
      }
    },
    "/v1/quotes": {
      "post": {
        "operationId": "createPartnerQuote",
        "summary": "Create an indicative quote",
        "description": "Creates an indicative quote for an available stay. The quoted price is not guaranteed and\nis not preserved during handoff; Hella revalidates availability and price before confirmation.\nThe returned absolute `handoffUrl` contains a signed, opaque partner reference. Partners\nmust use it unchanged and must not construct handoff links manually. Locale, stay, guests\nand attribution are bound inside the opaque `handoff` value rather than exposed as query fields.\nUnknown JSON fields are rejected. Malformed JSON returns `INVALID_JSON`; a body above the\naccepted request-size limit returns `PAYLOAD_TOO_LARGE`. Quote creation is not idempotent:\na retry can create a new `quoteId`, and this version does not accept an idempotency key.\nThe requested half-open stay may contain at most 60 local calendar nights.\n",
        "tags": [
          "Quotes"
        ],
        "x-idempotency": "non-idempotent",
        "x-cache-control": "no-store",
        "x-required-scopes": [
          "quotes:write"
        ],
        "x-max-stay-nights": 60,
        "x-error-codes": {
          "400": [
            "VALIDATION_ERROR",
            "INVALID_JSON"
          ],
          "401": [
            "PARTNER_AUTH_REQUIRED",
            "INVALID_PARTNER_CREDENTIAL",
            "PARTNER_ACCESS_REVOKED"
          ],
          "403": [
            "PARTNER_SCOPE_INSUFFICIENT",
            "ORIGIN_NOT_ALLOWED"
          ],
          "404": [
            "PROPERTY_NOT_FOUND"
          ],
          "409": [
            "STAY_UNAVAILABLE",
            "QUOTE_UNAVAILABLE"
          ],
          "413": [
            "PAYLOAD_TOO_LARGE"
          ],
          "429": [
            "RATE_LIMIT_EXCEEDED"
          ],
          "500": [
            "INTERNAL_ERROR"
          ],
          "503": [
            "PARTNER_API_UNAVAILABLE"
          ]
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePartnerQuoteRequest"
              },
              "examples": {
                "quote": {
                  "$ref": "#/components/examples/CreateQuoteRequestExample"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Indicative quote created.",
            "headers": {
              "Location": {
                "description": "Absolute handoff URL for this quote.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerQuote"
                },
                "examples": {
                  "quote": {
                    "$ref": "#/components/examples/QuoteExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/QuoteBadRequestProblem"
          },
          "401": {
            "$ref": "#/components/responses/AuthenticationProblem"
          },
          "403": {
            "$ref": "#/components/responses/AuthorisationProblem"
          },
          "404": {
            "$ref": "#/components/responses/PropertyNotFoundProblem"
          },
          "409": {
            "$ref": "#/components/responses/UnavailableProblem"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLargeProblem"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitProblem"
          },
          "500": {
            "$ref": "#/components/responses/InternalProblem"
          },
          "503": {
            "$ref": "#/components/responses/PartnerApiUnavailableProblem"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PartnerBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque partner token",
        "description": "Revocable partner credential for server-to-server use. Never expose it in browser code.\n"
      }
    },
    "parameters": {
      "PropertyId": {
        "name": "propertyId",
        "in": "path",
        "required": true,
        "description": "Opaque Hella property identifier. Do not parse or derive meaning from it.",
        "schema": {
          "$ref": "#/components/schemas/PropertyId"
        }
      },
      "City": {
        "name": "city",
        "in": "query",
        "required": true,
        "description": "Destination city name.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "example": "Roma"
      },
      "Country": {
        "name": "country",
        "in": "query",
        "required": false,
        "description": "ISO 3166-1 alpha-2 country code.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        },
        "example": "IT"
      },
      "CheckIn": {
        "name": "checkIn",
        "in": "query",
        "required": true,
        "description": "First occupied local calendar date.",
        "schema": {
          "$ref": "#/components/schemas/LocalDate"
        },
        "example": "2027-05-12"
      },
      "CheckOut": {
        "name": "checkOut",
        "in": "query",
        "required": true,
        "description": "Exclusive local-calendar end of the stay; must be after check-in and no more than 60 nights later.",
        "schema": {
          "$ref": "#/components/schemas/LocalDate"
        },
        "example": "2027-05-15"
      },
      "CheckInOptional": {
        "name": "checkIn",
        "in": "query",
        "required": false,
        "description": "First occupied local calendar date. Must be supplied together with checkOut.",
        "schema": {
          "$ref": "#/components/schemas/LocalDate"
        },
        "example": "2027-05-12"
      },
      "CheckOutOptional": {
        "name": "checkOut",
        "in": "query",
        "required": false,
        "description": "Exclusive local-calendar end date. Must be supplied with checkIn, later than it and no more than 60 nights later.",
        "schema": {
          "$ref": "#/components/schemas/LocalDate"
        },
        "example": "2027-05-15"
      },
      "Guests": {
        "name": "guests",
        "in": "query",
        "required": false,
        "description": "Total number of travellers.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 30,
          "default": 2
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "PageSize": {
        "name": "pageSize",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 50,
          "default": 20
        }
      },
      "Locale": {
        "name": "locale",
        "in": "query",
        "required": false,
        "description": "BCP 47 response locale.",
        "schema": {
          "type": "string",
          "enum": [
            "it-IT",
            "en-GB"
          ],
          "default": "it-IT"
        }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Maximum requests available to this partner credential in the current 60-second window.",
        "schema": {
          "type": "integer",
          "example": 120
        }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "example": 119
        }
      },
      "RateLimitReset": {
        "description": "Unix timestamp in seconds when the current window resets.",
        "schema": {
          "type": "integer",
          "format": "int64",
          "example": 1810000060
        }
      },
      "CorrelationId": {
        "description": "Opaque request identifier for support and tracing.",
        "schema": {
          "type": "string",
          "example": "req_01JEXAMPLE00000000000000"
        }
      },
      "CacheControl": {
        "description": "Cache policy for this response. Catalogue responses are private for 60 seconds; availability and quotes are not stored.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying the request.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "example": 42
        }
      }
    },
    "responses": {
      "ValidationProblem": {
        "description": "Request validation failed.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblem"
            },
            "examples": {
              "invalidDate": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/validation-error",
                  "title": "Invalid request",
                  "status": 400,
                  "detail": "One or more request fields are invalid.",
                  "code": "VALIDATION_ERROR",
                  "correlationId": "req_01JEXAMPLE00000000000000",
                  "errors": [
                    {
                      "field": "checkOut",
                      "code": "DATE_ORDER_INVALID",
                      "detail": "checkOut must be after checkIn."
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "QuoteBadRequestProblem": {
        "description": "The JSON body is malformed or one or more request fields are invalid.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ValidationProblem"
                },
                {
                  "$ref": "#/components/schemas/Problem"
                }
              ]
            },
            "examples": {
              "invalidFields": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/validation-error",
                  "title": "Invalid request",
                  "status": 400,
                  "detail": "One or more request fields are invalid.",
                  "code": "VALIDATION_ERROR",
                  "correlationId": "req_01JEXAMPLE00000000000000",
                  "errors": [
                    {
                      "field": "stay.checkOut",
                      "code": "DATE_ORDER_INVALID",
                      "detail": "checkOut must be after checkIn."
                    }
                  ]
                }
              },
              "invalidJson": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/invalid-json",
                  "title": "Invalid JSON",
                  "status": 400,
                  "detail": "The request body is not valid JSON.",
                  "code": "INVALID_JSON",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              }
            }
          }
        }
      },
      "AuthenticationProblem": {
        "description": "Partner credential is missing, invalid or revoked.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "missingCredential": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/partner-auth-required",
                  "title": "Authentication required",
                  "status": 401,
                  "detail": "Supply a valid partner credential.",
                  "code": "PARTNER_AUTH_REQUIRED",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              },
              "invalidCredential": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/invalid-partner-credential",
                  "title": "Invalid credential",
                  "status": 401,
                  "detail": "The supplied Partner API credential is not valid.",
                  "code": "INVALID_PARTNER_CREDENTIAL",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              },
              "revokedCredential": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/partner-access-revoked",
                  "title": "Partner access revoked",
                  "status": 401,
                  "detail": "This Partner API credential has been revoked.",
                  "code": "PARTNER_ACCESS_REVOKED",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              }
            }
          }
        }
      },
      "AuthorisationProblem": {
        "description": "The credential lacks the required scope or the browser origin is not authorized.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "insufficientScope": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/partner-scope-insufficient",
                  "title": "Access denied",
                  "status": 403,
                  "detail": "This partner credential cannot perform the requested operation.",
                  "code": "PARTNER_SCOPE_INSUFFICIENT",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              },
              "originNotAllowed": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/origin-not-allowed",
                  "title": "Origin not allowed",
                  "status": 403,
                  "detail": "This browser origin is not authorized for the Partner API.",
                  "code": "ORIGIN_NOT_ALLOWED",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              }
            }
          }
        }
      },
      "PropertyNotFoundProblem": {
        "description": "Property identifier is unknown or unavailable to this partner.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://www.hellastays.com/developers/problems/property-not-found",
              "title": "Property not found",
              "status": 404,
              "detail": "The requested property is not available.",
              "code": "PROPERTY_NOT_FOUND",
              "correlationId": "req_01JEXAMPLE00000000000000"
            }
          }
        }
      },
      "UnavailableProblem": {
        "description": "The requested stay is no longer available.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "stayUnavailable": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/stay-unavailable",
                  "title": "Stay unavailable",
                  "status": 409,
                  "detail": "The requested stay is not currently available.",
                  "code": "STAY_UNAVAILABLE",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              },
              "quoteUnavailable": {
                "value": {
                  "type": "https://www.hellastays.com/developers/problems/quote-unavailable",
                  "title": "Quote unavailable",
                  "status": 409,
                  "detail": "An indicative quote cannot be created for this property.",
                  "code": "QUOTE_UNAVAILABLE",
                  "correlationId": "req_01JEXAMPLE00000000000000"
                }
              }
            }
          }
        }
      },
      "RateLimitProblem": {
        "description": "The 300/minute source-IP ingress limit or the authenticated partner-credential quota was exceeded. Retry-After is authoritative; X-RateLimit-* headers, when present, describe the credential quota.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://www.hellastays.com/developers/problems/rate-limit-exceeded",
              "title": "Too many requests",
              "status": 429,
              "detail": "Retry after the interval indicated by Retry-After.",
              "code": "RATE_LIMIT_EXCEEDED",
              "correlationId": "req_01JEXAMPLE00000000000000"
            }
          }
        }
      },
      "PayloadTooLargeProblem": {
        "description": "The JSON request body exceeds the accepted request-size limit.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://www.hellastays.com/developers/problems/payload-too-large",
              "title": "Payload too large",
              "status": 413,
              "detail": "The request body exceeds the accepted size.",
              "code": "PAYLOAD_TOO_LARGE",
              "correlationId": "req_01JEXAMPLE00000000000000"
            }
          }
        }
      },
      "InternalProblem": {
        "description": "An unexpected error occurred. The response never exposes internal details.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://www.hellastays.com/developers/problems/internal-error",
              "title": "Internal server error",
              "status": 500,
              "detail": "The request could not be completed.",
              "code": "INTERNAL_ERROR",
              "correlationId": "req_01JEXAMPLE00000000000000"
            }
          }
        }
      },
      "PartnerApiUnavailableProblem": {
        "description": "The Partner API is temporarily unavailable.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://www.hellastays.com/developers/problems/partner-api-unavailable",
              "title": "Partner API unavailable",
              "status": 503,
              "detail": "The Partner API is temporarily unavailable.",
              "code": "PARTNER_API_UNAVAILABLE",
              "correlationId": "req_01JEXAMPLE00000000000000"
            }
          }
        }
      }
    },
    "schemas": {
      "PropertyId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 160,
        "pattern": "^[A-Za-z0-9._~-]+$",
        "example": "sandbox-roma-001"
      },
      "LocalDate": {
        "type": "string",
        "format": "date",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        "description": "A real Gregorian calendar date in YYYY-MM-DD format."
      },
      "DecimalMoney": {
        "type": "string",
        "pattern": "^(0|[1-9]\\d*)\\.\\d{2}$",
        "description": "Non-negative decimal amount with exactly two fractional digits.",
        "example": "615.00"
      },
      "Currency": {
        "type": "string",
        "pattern": "^[A-Z]{3}$",
        "description": "ISO 4217 currency code.",
        "example": "EUR"
      },
      "PartnerLocation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "city",
          "country",
          "area"
        ],
        "properties": {
          "city": {
            "type": "string",
            "example": "Roma"
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "example": "IT"
          },
          "area": {
            "type": [
              "string",
              "null"
            ],
            "description": "Approximate neighbourhood or area; never an exact address.",
            "example": "Centro storico"
          }
        }
      },
      "PartnerCapacity": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "guests",
          "bathrooms"
        ],
        "properties": {
          "guests": {
            "type": "integer",
            "minimum": 1,
            "example": 4
          },
          "bathrooms": {
            "type": "number",
            "minimum": 0,
            "multipleOf": 0.5,
            "example": 1
          }
        }
      },
      "PartnerRating": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "score",
          "count"
        ],
        "properties": {
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 5,
            "example": 4.8
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "example": 24
          }
        }
      },
      "PartnerPropertySummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "propertyId",
          "name",
          "description",
          "propertyType",
          "location",
          "capacity",
          "amenities",
          "rating",
          "startingPrice",
          "currency",
          "timezone"
        ],
        "properties": {
          "propertyId": {
            "$ref": "#/components/schemas/PropertyId"
          },
          "name": {
            "type": "string",
            "example": "Casa Demo Roma"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "Appartamento sintetico usato esclusivamente dal mock locale."
          },
          "propertyType": {
            "type": "string",
            "example": "apartment"
          },
          "location": {
            "$ref": "#/components/schemas/PartnerLocation"
          },
          "capacity": {
            "$ref": "#/components/schemas/PartnerCapacity"
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "wifi",
              "air-conditioning"
            ]
          },
          "rating": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PartnerRating"
              },
              {
                "type": "null"
              }
            ]
          },
          "startingPrice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DecimalMoney"
              },
              {
                "type": "null"
              }
            ]
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone used for calendar-date semantics.",
            "example": "Europe/Rome"
          }
        }
      },
      "PartnerPropertyDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PartnerPropertySummary"
          }
        ]
      },
      "Pagination": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "page",
          "pageSize",
          "total",
          "hasMore"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1,
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "example": 20
          },
          "total": {
            "type": "integer",
            "minimum": 0,
            "example": 1
          },
          "hasMore": {
            "type": "boolean",
            "example": false
          }
        }
      },
      "PartnerPropertyList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "items",
          "pagination"
        ],
        "properties": {
          "items": {
            "type": "array",
            "description": "Properties ordered deterministically by opaque propertyId before pagination.",
            "items": {
              "$ref": "#/components/schemas/PartnerPropertySummary"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Stay": {
        "type": "object",
        "x-max-stay-nights": 60,
        "additionalProperties": false,
        "required": [
          "checkIn",
          "checkOut",
          "nights",
          "guests",
          "timezone"
        ],
        "properties": {
          "checkIn": {
            "$ref": "#/components/schemas/LocalDate"
          },
          "checkOut": {
            "$ref": "#/components/schemas/LocalDate"
          },
          "nights": {
            "type": "integer",
            "minimum": 1,
            "maximum": 60,
            "description": "Number of occupied local calendar nights in the half-open stay interval.",
            "example": 3
          },
          "guests": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "description": "Number of travellers used for the availability or quote decision.",
            "example": 2
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone defining the local-calendar interpretation of checkIn and checkOut.",
            "example": "Europe/Rome"
          }
        }
      },
      "AvailabilityDay": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "date",
          "available"
        ],
        "properties": {
          "date": {
            "$ref": "#/components/schemas/LocalDate"
          },
          "available": {
            "type": "boolean",
            "description": "Whether at least one eligible inventory type is available on this local calendar night. Do not combine day flags to infer whole-stay availability."
          }
        }
      },
      "PartnerAvailability": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "propertyId",
          "stay",
          "available",
          "days"
        ],
        "properties": {
          "propertyId": {
            "$ref": "#/components/schemas/PropertyId"
          },
          "stay": {
            "$ref": "#/components/schemas/Stay"
          },
          "available": {
            "type": "boolean",
            "description": "True only when one eligible inventory type can host the requested guests for every night in the interval without switching types."
          },
          "days": {
            "type": "array",
            "description": "One diagnostic entry per occupied local calendar night in [checkIn, checkOut), interpreted in stay.timezone; check-out is excluded.",
            "items": {
              "$ref": "#/components/schemas/AvailabilityDay"
            }
          }
        }
      },
      "QuoteStayRequest": {
        "type": "object",
        "x-max-stay-nights": 60,
        "description": "Half-open local-calendar stay in the property's IANA timezone, with at most 60 occupied nights.",
        "additionalProperties": false,
        "required": [
          "checkIn",
          "checkOut",
          "guests"
        ],
        "properties": {
          "checkIn": {
            "$ref": "#/components/schemas/LocalDate"
          },
          "checkOut": {
            "$ref": "#/components/schemas/LocalDate"
          },
          "guests": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "example": 2
          }
        }
      },
      "CreatePartnerQuoteRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "propertyId",
          "stay",
          "locale"
        ],
        "properties": {
          "propertyId": {
            "$ref": "#/components/schemas/PropertyId"
          },
          "stay": {
            "$ref": "#/components/schemas/QuoteStayRequest"
          },
          "locale": {
            "type": "string",
            "enum": [
              "it-IT",
              "en-GB"
            ],
            "example": "it-IT"
          }
        }
      },
      "PartnerPricing": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "accommodation",
          "cleaningFee",
          "taxes",
          "total",
          "pricePreservedInHandoff"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "const": "indicative",
            "description": "Quotes are indicative and revalidated by Hella."
          },
          "accommodation": {
            "$ref": "#/components/schemas/DecimalMoney"
          },
          "cleaningFee": {
            "$ref": "#/components/schemas/DecimalMoney"
          },
          "taxes": {
            "$ref": "#/components/schemas/DecimalMoney"
          },
          "total": {
            "$ref": "#/components/schemas/DecimalMoney"
          },
          "pricePreservedInHandoff": {
            "type": "boolean",
            "const": false,
            "description": "The price is not preserved and is recalculated during handoff."
          }
        }
      },
      "PartnerQuote": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "quoteId",
          "propertyId",
          "stay",
          "pricing",
          "currency",
          "expiresAt",
          "handoffUrl"
        ],
        "properties": {
          "quoteId": {
            "type": "string",
            "description": "Opaque quote identifier.",
            "example": "quote_sandbox_01"
          },
          "propertyId": {
            "$ref": "#/components/schemas/PropertyId"
          },
          "stay": {
            "$ref": "#/components/schemas/Stay"
          },
          "pricing": {
            "$ref": "#/components/schemas/PartnerPricing"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Expiry instant in UTC. Expiry does not make an indicative price guaranteed.",
            "example": "2027-05-01T12:15:00.000Z"
          },
          "handoffUrl": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Absolute Hella URL containing one opaque signed handoff value. Use unchanged.",
            "example": "https://www.app.hellastays.com/app/property/sandbox-roma-001?handoff=mock_signed_reference"
          }
        }
      },
      "ProblemCode": {
        "type": "string",
        "description": "Stable machine-readable application error code. Branch on this value, never on prose.",
        "enum": [
          "VALIDATION_ERROR",
          "INVALID_JSON",
          "PAYLOAD_TOO_LARGE",
          "PARTNER_AUTH_REQUIRED",
          "INVALID_PARTNER_CREDENTIAL",
          "PARTNER_ACCESS_REVOKED",
          "PARTNER_SCOPE_INSUFFICIENT",
          "ORIGIN_NOT_ALLOWED",
          "PROPERTY_NOT_FOUND",
          "STAY_UNAVAILABLE",
          "QUOTE_UNAVAILABLE",
          "RATE_LIMIT_EXCEEDED",
          "PARTNER_API_UNAVAILABLE",
          "PARTNER_OPERATION_NOT_FOUND",
          "HANDOFF_INVALID",
          "INTERNAL_ERROR"
        ]
      },
      "Problem": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "correlationId"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          },
          "detail": {
            "type": "string",
            "description": "Human-readable context; clients must branch on code, not this text."
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode"
          },
          "correlationId": {
            "type": "string",
            "description": "Opaque support identifier."
          }
        }
      },
      "ValidationIssue": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "field",
          "code",
          "detail"
        ],
        "properties": {
          "field": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]+$"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "ValidationProblem": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "correlationId",
          "errors"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "const": 400
          },
          "detail": {
            "type": "string",
            "description": "Human-readable context; clients must branch on code, not this text."
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode",
            "const": "VALIDATION_ERROR"
          },
          "correlationId": {
            "type": "string",
            "description": "Opaque support identifier."
          },
          "errors": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ValidationIssue"
            }
          }
        }
      }
    },
    "examples": {
      "PropertyListExample": {
        "value": {
          "items": [
            {
              "propertyId": "sandbox-roma-001",
              "name": "Casa Demo Roma",
              "description": "Appartamento sintetico usato esclusivamente dal mock locale.",
              "propertyType": "apartment",
              "location": {
                "city": "Roma",
                "country": "IT",
                "area": "Centro"
              },
              "capacity": {
                "guests": 4,
                "bathrooms": 1
              },
              "amenities": [
                "wifi",
                "air-conditioning"
              ],
              "rating": {
                "score": 4.8,
                "count": 24
              },
              "startingPrice": "205.00",
              "currency": "EUR",
              "timezone": "Europe/Rome"
            }
          ],
          "pagination": {
            "page": 1,
            "pageSize": 20,
            "total": 1,
            "hasMore": false
          }
        }
      },
      "PropertyDetailExample": {
        "value": {
          "propertyId": "sandbox-roma-001",
          "name": "Casa Demo Roma",
          "description": "Appartamento sintetico usato esclusivamente dal mock locale.",
          "propertyType": "apartment",
          "location": {
            "city": "Roma",
            "country": "IT",
            "area": "Centro"
          },
          "capacity": {
            "guests": 4,
            "bathrooms": 1
          },
          "amenities": [
            "wifi",
            "air-conditioning"
          ],
          "rating": {
            "score": 4.8,
            "count": 24
          },
          "startingPrice": "205.00",
          "currency": "EUR",
          "timezone": "Europe/Rome"
        }
      },
      "AvailabilityExample": {
        "value": {
          "propertyId": "sandbox-roma-001",
          "stay": {
            "checkIn": "2027-05-12",
            "checkOut": "2027-05-15",
            "nights": 3,
            "guests": 2,
            "timezone": "Europe/Rome"
          },
          "available": true,
          "days": [
            {
              "date": "2027-05-12",
              "available": true
            },
            {
              "date": "2027-05-13",
              "available": true
            },
            {
              "date": "2027-05-14",
              "available": true
            }
          ]
        }
      },
      "CreateQuoteRequestExample": {
        "value": {
          "propertyId": "sandbox-roma-001",
          "stay": {
            "checkIn": "2027-05-12",
            "checkOut": "2027-05-15",
            "guests": 2
          },
          "locale": "it-IT"
        }
      },
      "QuoteExample": {
        "value": {
          "quoteId": "quote_sandbox_01",
          "propertyId": "sandbox-roma-001",
          "stay": {
            "checkIn": "2027-05-12",
            "checkOut": "2027-05-15",
            "nights": 3,
            "guests": 2,
            "timezone": "Europe/Rome"
          },
          "pricing": {
            "mode": "indicative",
            "accommodation": "615.00",
            "cleaningFee": "0.00",
            "taxes": "0.00",
            "total": "615.00",
            "pricePreservedInHandoff": false
          },
          "currency": "EUR",
          "expiresAt": "2027-05-01T12:15:00.000Z",
          "handoffUrl": "https://www.app.hellastays.com/app/property/sandbox-roma-001?handoff=mock_signed_reference"
        }
      }
    }
  },
  "x-rate-limit-policy": {
    "scope": "partner credential",
    "defaultLimit": 120,
    "windowSeconds": 60,
    "authoritativeHeaders": [
      "X-RateLimit-Limit",
      "X-RateLimit-Remaining",
      "X-RateLimit-Reset"
    ],
    "ingress": {
      "scope": "source IP",
      "limit": 300,
      "windowSeconds": 60,
      "description": "Safety limit applied before partner credential authentication; Retry-After remains authoritative when this limit returns 429."
    },
    "corsExposedHeaders": [
      "X-Correlation-ID",
      "X-RateLimit-Limit",
      "X-RateLimit-Remaining",
      "X-RateLimit-Reset",
      "Retry-After",
      "Location"
    ]
  }
}
