{
  "openapi": "3.1.0",
  "info": {
    "title": "Quarters API",
    "version": "1.0.0",
    "description": "Published listings, live availability, reservations, tenancy agreements and inquiries, with signed webhooks. Documented at https://developers.quarters.live; checked against the running implementation on 2026-09-13.",
    "contact": {
      "email": "info@quarters.live"
    }
  },
  "servers": [
    {
      "url": "https://go.quarters.live/api/v1"
    }
  ],
  "security": [
    {
      "oauth2": []
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "OAuth 2.0 client credentials, the same grant Guesty’s Open API uses, so an integration written against one needs a different token URL and nothing else."
    },
    {
      "name": "Listings",
      "description": "Property content as the Publish button froze it. A property that has never been published does not exist here, and content being edited right now never leaks. Only the calendar is live."
    },
    {
      "name": "Reservations",
      "description": "Bookings, in every status. Read them to drive contracts, accounting or a guest portal. The embedded property block is the flat’s identity as it stands today, name, address and owner, because that is what a tenancy agreement must state; listing content stays behind the publish boundary and comes from /listings."
    },
    {
      "name": "Agreements",
      "description": "The tenancy agreement on a booking. Reading works for every organisation. Writing is for one kind of organisation only: one that has chosen, under Settings › Contracts, to issue and sign its contracts in another system (DocuSeal, say) and let Quarters mirror what that system reports. While that setting is on, nothing in Quarters sends a contract; the dashboard shows these rows and offers staff one button, Request contract, which arrives with you as an `agreement.requested` webhook."
    },
    {
      "name": "Inquiries",
      "description": "Your booking form lands here, as a row staff convert to a reservation in one click and resolve to a guest record, rather than as an email somebody re-types."
    }
  ],
  "paths": {
    "/oauth2/token": {
      "post": {
        "operationId": "token",
        "tags": [
          "Authentication"
        ],
        "summary": "Exchange client credentials for an access token",
        "description": "Send the form as application/x-www-form-urlencoded. HTTP Basic with client_id:client_secret is accepted too, and so is a JSON body with the same three fields.\n\nThe answer is a bearer token that lasts 24 hours. Cache it and re-use it until a 401 says it expired, then mint another. Revoking the credentials under Settings › Integrations invalidates every token issued from them at once.\n\nThe response carries Cache-Control: no-store and Pragma: no-cache, as the RFC asks. Cache the token in your own process, not in an HTTP cache.\n\nTokens are per credential set. A second set for a second system gets its own tokens, and revoking one leaves the other untouched.",
        "security": [],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exchange client credentials for an access token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "unsupported_grant_type: Anything but grant_type=client_credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "invalid_client: Unknown client id, wrong secret, or credentials that were revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/listings": {
      "get": {
        "operationId": "list-listings",
        "tags": [
          "Listings"
        ],
        "summary": "List every published listing",
        "description": "The full set of published properties for the credentials’ organisation, sorted by nickname. Not paginated and not a delta: if you mirror it, treat the response as the complete set and delete rows it no longer contains. That is how an unpublished listing disappears from your site.\n\n`description` fields are rich text, HTML from the editor; `title` is plain. `translations` carries the same six fields per site language, and a consumer falls back to `description` for anything missing.\n\n`bedrooms`, `beds` and `bathrooms` are derived from `rooms` and carried so a listing card does not have to reduce an array. `bathrooms` can be fractional.\n\nImage URLs are absolute. Four renditions per photo: `thumbnail`, `regular`, `large` and the `original`.\n\nMoney is integer euro-cents; a null price means the operator has not set one.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "List every published listing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "listings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Listing"
                      }
                    }
                  },
                  "required": [
                    "listings"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/listings/{id}": {
      "get": {
        "operationId": "get-listing",
        "tags": [
          "Listings"
        ],
        "summary": "Retrieve one listing",
        "description": "The same object the list serves, for one property.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The listing id, from /listings.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retrieve one listing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "listing": {
                      "$ref": "#/components/schemas/Listing"
                    }
                  },
                  "required": [
                    "listing"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown to your organisation, or never published. The two are indistinguishable on purpose.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/listings/{id}/calendar": {
      "get": {
        "operationId": "get-calendar",
        "tags": [
          "Listings"
        ],
        "summary": "Retrieve live availability",
        "description": "Per-day availability, computed on request from reservations and calendar blocks. This is the one read that is not snapshot-bound: a booking taken thirty seconds ago is reflected immediately, because a stale calendar double-books a nine-month tenancy.\n\n`booked` is a confirmed tenancy; `reserved` is held but not yet firm; `unavailable` is a calendar block (renovation, owner use); `available` is bookable.\n\nEnquiries and options never block a day. Cancelling releases the days immediately.\n\nCheck-out is exclusive. A tenancy ending on the 30th leaves the 30th available, because the handover is at noon that day.\n\n`minNights` is the listing’s base minimum and `isBaseMinNights` is always true today; both are carried so a consumer written against per-day minimums keeps working if they ever vary.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The listing id. It must be published.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "Defaults to today in the listing’s timezone.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "Inclusive. Defaults to start + 730 days. The range may span at most 1100 days.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retrieve live availability",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "days": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarDay"
                      }
                    }
                  },
                  "required": [
                    "days"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request: A date that is not YYYY-MM-DD, end before start, or a span over 1100 days.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown or unpublished listing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/reservations": {
      "get": {
        "operationId": "list-reservations",
        "tags": [
          "Reservations"
        ],
        "summary": "List reservations",
        "description": "Offset pagination on a stable order: the sort key, then id. `total` counts the same filters, so you can page to the end without probing.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated: inquiry, reserved, confirmed, cancelled. Default all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "propertyId",
            "in": "query",
            "required": false,
            "description": "One property.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "checkInFrom",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on check-in.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "checkInTo",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on check-in.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "checkOutFrom",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on check-out.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "checkOutTo",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on check-out.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "required": false,
            "description": "Only bookings changed at or after this instant. The incremental-sync filter: keep the `updatedAt` of the last row you saw and ask from there.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Default checkIn.",
            "schema": {
              "type": "string",
              "enum": [
                "checkIn",
                "checkOut",
                "updatedAt",
                "createdAt"
              ]
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Default asc.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1–100, default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "skip",
            "in": "query",
            "required": false,
            "description": "Rows to skip, default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List reservations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "reservations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Rows matching the same filters, across every page."
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "skip": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "reservations",
                    "total",
                    "limit",
                    "skip"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request: An unknown status, a malformed date or timestamp, a sort key that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/reservations/{id}": {
      "get": {
        "operationId": "get-reservation",
        "tags": [
          "Reservations"
        ],
        "summary": "Retrieve one reservation",
        "description": "The booking with its guest, the property’s identity, the money as agreed at booking time, and the current agreement if there is one. `dashboardUrl` opens the same record in the dashboard, for an alert email or a back-office link.\n\n`pricing` is snapshotted when the booking is made, so a later rate change on the listing never rewrites an agreed booking. `monthlyCents` is rent + utilities + furniture when all three are set.\n\n`nights` is check-out minus check-in. `minStayMonths` is what the agreement states as the minimum term.\n\n`requiresContract` says whether this booking needs a signed agreement before it counts as confirmed; it is resolved once at booking time.\n\nThe lifecycle stamps (`inquiredAt`, `reservedAt`, `confirmedAt`, `cancelledAt`) record when a status was entered and are never cleared.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The reservation id, from the list or a webhook.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retrieve one reservation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "reservation": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  },
                  "required": [
                    "reservation"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown to your organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/agreement": {
      "get": {
        "operationId": "get-agreement",
        "tags": [
          "Agreements"
        ],
        "summary": "Retrieve the current agreement",
        "description": "The booking’s current agreement, or null when none has been issued. Superseded versions are not listed; the current one carries its `version`.\n\n`provider` is `quarters` for an envelope Quarters issued itself and `external` for one written through this API. A Quarters signer never exposes its signing link here; an external one echoes the `signingUrl` you reported.\n\n`document` is set once the executed PDF has been archived, by Quarters for its own envelopes and by you through the document endpoint for yours. `documentUrl` is the copy hosted by the external provider, if you reported one.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The reservation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retrieve the current agreement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agreement": {
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/Agreement"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "agreement"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown reservation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "put-agreement",
        "tags": [
          "Agreements"
        ],
        "summary": "Report an external agreement",
        "description": "Your whole current view of the envelope. The same `externalId` as the current agreement updates it in place; a different one, or the first, creates a new version and supersedes a live one, exactly as a re-issue from the dashboard would.\n\nSigners are matched by role and never removed. A report about one party says nothing about the others, so you may send only the party that changed.\n\n201 when the call created a new version, 200 when it updated the current one in place.\n\nThe moment `status` becomes `completed`, a booking that is `reserved` becomes `confirmed`, the same consequence a signature in Quarters has. You do not have to change the booking status yourself, and you receive a `reservation.updated` webhook for it.\n\n`declined`, `voided` and `failed` are recorded and shown to staff; none of them changes the booking. Quarters sends no email on any of these: your system already talked to the parties.\n\nReporting `sent` with a new externalId while a `sent` agreement is current marks the old one `superseded`. A `completed` one is kept in the history untouched.\n\nA report whose externalId belongs to a superseded version lands on that historical row: it records what happened, but changes nothing about the current agreement or the booking. Finishing an old envelope after staff re-issued never confirms anything.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The reservation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgreementInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Report an external agreement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agreement": {
                      "$ref": "#/components/schemas/Agreement"
                    }
                  },
                  "required": [
                    "agreement"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request: A bad field, named in the message; a missing externalId on a non-failed status; or a role listed twice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown reservation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict: The organisation signs its contracts in Quarters (switch the provider under Settings › Contracts first), or a failure without an externalId arrived while a sent or completed envelope is current. Report it against that envelope’s id instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/agreement/document": {
      "put": {
        "operationId": "put-agreement-document",
        "tags": [
          "Agreements"
        ],
        "summary": "Archive the executed PDF",
        "description": "The signed document, stored by Quarters next to the agreement so it outlives the provider’s retention and the dashboard can serve it. Send the bytes as application/pdf, at most 25 MB, not a link: a URL to fetch would be a request from inside Quarters to wherever the caller says.\n\nUploading again replaces the archived copy and its checksum.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The reservation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Archive the executed PDF",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agreement": {
                      "$ref": "#/components/schemas/Agreement"
                    }
                  },
                  "required": [
                    "agreement"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown reservation, or one without an agreement.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict: The current agreement is a Quarters envelope (it has its own document), or is not completed yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "payload_too_large: Over 25 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "unsupported_media_type: Not application/pdf, or the bytes are not a PDF.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "get-agreement-document",
        "tags": [
          "Agreements"
        ],
        "summary": "Retrieve the archived PDF",
        "description": "The bytes archived for the current agreement, as application/pdf.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The reservation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retrieve the archived PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: Unknown reservation, or no document has been archived for its current agreement.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/inquiries": {
      "post": {
        "operationId": "create-inquiry",
        "tags": [
          "Inquiries"
        ],
        "summary": "Create an inquiry",
        "description": "Anything your form collects beyond the fields below travels in `metadata` untyped, so the form can evolve without a contract change.\n\n`guestCount` is still accepted and folded into `adults`. It is deprecated; send the age bands.\n\nAn inquiry never blocks a day. It becomes a booking when staff convert it.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Create an inquiry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The inquiry’s id."
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request: A bad field, with a message naming its path. An unknown or unpublished propertyId is a 400 too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No, expired or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: More than the organisation’s daily allowance (500 by default) in 24 hours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://go.quarters.live/api/v1/oauth2/token",
            "scopes": {}
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "A stable slug for the failure class. Switch on it."
          },
          "message": {
            "type": "string",
            "description": "A sentence for a person. Names the field where one is at fault."
          }
        },
        "required": [
          "error",
          "message"
        ],
        "description": "Every failure on a resource endpoint. A 401 also carries WWW-Authenticate: Bearer."
      },
      "TokenRequest": {
        "type": "object",
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "client_credentials",
            "description": "Always client_credentials."
          },
          "client_id": {
            "type": "string",
            "description": "The client ID from Settings › Integrations, e.g. qc_…"
          },
          "client_secret": {
            "type": "string",
            "description": "The secret shown once when the credentials were created, e.g. qs_…"
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "description": "The form posted to the token endpoint. HTTP Basic with client_id:client_secret is accepted in place of the two fields, and so is the same form as a JSON object."
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "access_token": {
            "type": "string",
            "description": "The bearer, e.g. qat_… Send it as Authorization: Bearer on every call."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until the token expires. Always 86400."
          }
        },
        "required": [
          "token_type",
          "access_token",
          "expires_in"
        ],
        "description": "A bearer token, good for 24 hours."
      },
      "OAuthError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "An RFC 6749 error code."
          },
          "error_description": {
            "type": "string",
            "description": "A sentence for a person."
          }
        },
        "required": [
          "error",
          "error_description"
        ],
        "description": "The token endpoint fails in RFC 6749’s own shape rather than the API’s, because that is what an OAuth client library reads."
      },
      "Address": {
        "type": "object",
        "properties": {
          "street": {
            "type": [
              "string",
              "null"
            ]
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2."
          },
          "area": {
            "type": [
              "string",
              "null"
            ],
            "description": "The neighbourhood, as the operator names it."
          },
          "latitude": {
            "type": [
              "number",
              "null"
            ]
          },
          "longitude": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "street",
          "postalCode",
          "city",
          "country",
          "area",
          "latitude",
          "longitude"
        ],
        "description": "Where the flat is."
      },
      "Bed": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "unspecified",
              "single",
              "double",
              "queen",
              "king",
              "bunk_bed",
              "sofa_bed",
              "crib"
            ]
          },
          "count": {
            "type": "integer"
          }
        },
        "required": [
          "type",
          "count"
        ],
        "description": "One kind of bed in a room, with how many of it there are."
      },
      "Room": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "bedroom",
              "living_room",
              "full_bathroom",
              "half_bathroom",
              "other"
            ]
          },
          "name": {
            "type": "string",
            "description": "Empty when the room has no name of its own."
          },
          "beds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bed"
            }
          }
        },
        "required": [
          "kind",
          "name",
          "beds"
        ],
        "description": "A room of the flat. Bathrooms carry no beds; a living room may."
      },
      "Image": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "isPrimary": {
            "type": "boolean",
            "description": "The cover photo. Exactly one image is primary."
          },
          "caption": {
            "type": [
              "string",
              "null"
            ]
          },
          "sortOrder": {
            "type": "integer",
            "description": "The operator’s order, ascending."
          },
          "width": {
            "type": "integer",
            "description": "Pixels, of the original."
          },
          "height": {
            "type": "integer",
            "description": "Pixels, of the original."
          },
          "bytes": {
            "type": "integer",
            "description": "Size of the original."
          },
          "thumbnail": {
            "type": "string",
            "format": "uri",
            "description": "Absolute."
          },
          "regular": {
            "type": "string",
            "format": "uri",
            "description": "Absolute."
          },
          "large": {
            "type": "string",
            "format": "uri",
            "description": "Absolute."
          },
          "original": {
            "type": "string",
            "format": "uri",
            "description": "Absolute."
          }
        },
        "required": [
          "id",
          "isPrimary",
          "caption",
          "sortOrder",
          "width",
          "height",
          "bytes",
          "thumbnail",
          "regular",
          "large",
          "original"
        ],
        "description": "A photo, in four renditions. URLs are absolute and stable until the listing is re-published."
      },
      "Description": {
        "type": "object",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain text."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rich text, as HTML from the editor."
          },
          "space": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rich text."
          },
          "neighborhood": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rich text."
          },
          "access": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rich text."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rich text."
          }
        },
        "required": [
          "title",
          "summary",
          "space",
          "neighborhood",
          "access",
          "notes"
        ],
        "description": "The six prose fields of a listing. `title` is plain; the rest is the editor’s HTML."
      },
      "Listing": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque. The same id a reservation’s `propertyId` carries."
          },
          "slug": {
            "type": "string"
          },
          "nickname": {
            "type": "string",
            "description": "The operator’s short name for the flat."
          },
          "active": {
            "type": "boolean"
          },
          "isListed": {
            "type": "boolean"
          },
          "propertyType": {
            "type": "string",
            "description": "Apartment, House, … as the operator classified it."
          },
          "roomType": {
            "type": "string",
            "description": "Entire home/apt, Private room, …"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "accommodates": {
            "type": "integer",
            "description": "How many people the flat sleeps."
          },
          "rooms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Room"
            }
          },
          "bedrooms": {
            "type": "integer",
            "description": "Derived from `rooms`, carried so a card does not have to reduce an array."
          },
          "beds": {
            "type": "integer",
            "description": "Derived from `rooms`."
          },
          "bathrooms": {
            "type": "number",
            "description": "Derived from `rooms`. Fractional for half bathrooms."
          },
          "squareMeter": {
            "type": [
              "integer",
              "null"
            ]
          },
          "timezone": {
            "type": "string",
            "description": "The IANA zone the listing’s dates are local to."
          },
          "pricing": {
            "type": "object",
            "properties": {
              "currency": {
                "type": "string",
                "const": "EUR"
              },
              "monthlyRateCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly rent. Integer euro-cents."
              },
              "depositCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Security deposit. Integer euro-cents."
              },
              "utilitiesCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly utilities. Integer euro-cents."
              },
              "furnitureCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly furniture fee. Integer euro-cents."
              }
            },
            "required": [
              "currency",
              "monthlyRateCents",
              "depositCents",
              "utilitiesCents",
              "furnitureCents"
            ]
          },
          "terms": {
            "type": "object",
            "properties": {
              "minNights": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The shortest stay the operator accepts."
              },
              "maxNights": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "minNights",
              "maxNights"
            ]
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Amenity keys, e.g. wifi, washer, dishwasher."
          },
          "description": {
            "$ref": "#/components/schemas/Description"
          },
          "translations": {
            "type": "object",
            "description": "The same six fields per site language, keyed by language code. Fall back to `description` for anything missing.",
            "additionalProperties": {
              "$ref": "#/components/schemas/Description"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Image"
            },
            "description": "Sorted by `sortOrder`; the primary photo first is not guaranteed."
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the Publish button froze this content."
          }
        },
        "required": [
          "id",
          "slug",
          "nickname",
          "active",
          "isListed",
          "propertyType",
          "roomType",
          "address",
          "accommodates",
          "rooms",
          "bedrooms",
          "beds",
          "bathrooms",
          "squareMeter",
          "timezone",
          "pricing",
          "terms",
          "amenities",
          "description",
          "translations",
          "images",
          "publishedAt"
        ],
        "description": "A property as the Publish button froze it. Content being edited right now never appears here; only the calendar is live."
      },
      "CalendarDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "reserved",
              "booked",
              "unavailable"
            ],
            "description": "`booked` is a confirmed tenancy; `reserved` is held but not yet firm; `unavailable` is a calendar block such as a renovation; `available` is bookable."
          },
          "minNights": {
            "type": "integer",
            "description": "The listing’s base minimum."
          },
          "isBaseMinNights": {
            "type": "boolean",
            "description": "Always true today; carried so a consumer written against per-day minimums keeps working if they ever vary."
          }
        },
        "required": [
          "date",
          "status",
          "minNights",
          "isBaseMinNights"
        ],
        "description": "One day of a listing’s availability."
      },
      "ReservationStatus": {
        "type": "string",
        "enum": [
          "inquiry",
          "reserved",
          "confirmed",
          "cancelled"
        ],
        "description": "`inquiry` is a request that blocks nothing; `reserved` is held; `confirmed` is firm; `cancelled` releases its days."
      },
      "PropertyIdentity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "nickname": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postalCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2."
              }
            },
            "required": [
              "street",
              "postalCode",
              "city",
              "country"
            ]
          },
          "ownerName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The lessor, as a tenancy agreement must name them."
          },
          "timezone": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "nickname",
          "slug",
          "address",
          "ownerName",
          "timezone"
        ],
        "description": "The flat’s identity as it stands today: name, address, owner. Read live, because that is what a tenancy agreement must state. Listing content stays behind the publish boundary and comes from /listings."
      },
      "Guest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firstName": {
            "type": [
              "string",
              "null"
            ]
          },
          "lastName": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "First and last name, joined."
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "firstName",
          "lastName",
          "name",
          "email",
          "phone",
          "company"
        ],
        "description": "The person the booking is for."
      },
      "Reservation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque."
          },
          "status": {
            "$ref": "#/components/schemas/ReservationStatus"
          },
          "propertyId": {
            "type": "string"
          },
          "property": {
            "$ref": "#/components/schemas/PropertyIdentity"
          },
          "guest": {
            "$ref": "#/components/schemas/Guest"
          },
          "checkIn": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "checkOut": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Exclusive: the handover is at noon that day."
          },
          "nights": {
            "type": "integer",
            "description": "Check-out minus check-in."
          },
          "adults": {
            "type": [
              "integer",
              "null"
            ]
          },
          "children": {
            "type": [
              "integer",
              "null"
            ]
          },
          "infants": {
            "type": [
              "integer",
              "null"
            ]
          },
          "pets": {
            "type": "boolean"
          },
          "pricing": {
            "type": "object",
            "properties": {
              "currency": {
                "type": "string",
                "const": "EUR"
              },
              "rentCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly rent as agreed. Integer euro-cents."
              },
              "utilitiesCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly utilities as agreed. Integer euro-cents."
              },
              "furnitureCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Monthly furniture fee as agreed. Integer euro-cents."
              },
              "monthlyCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Rent + utilities + furniture, when all three are set. Integer euro-cents."
              },
              "depositCents": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Security deposit as agreed. Integer euro-cents."
              },
              "minStayMonths": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "What the agreement states as the minimum term."
              }
            },
            "required": [
              "currency",
              "rentCents",
              "utilitiesCents",
              "furnitureCents",
              "monthlyCents",
              "depositCents",
              "minStayMonths"
            ],
            "description": "Snapshotted when the booking was made. A later rate change on the listing never rewrites an agreed booking."
          },
          "requiresContract": {
            "type": "boolean",
            "description": "Whether this booking needs a signed agreement before it counts as confirmed. Resolved once, at booking time."
          },
          "invoicePaid": {
            "type": "boolean"
          },
          "agreement": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Agreement"
              },
              {
                "type": "null"
              }
            ]
          },
          "dashboardUrl": {
            "type": "string",
            "format": "uri",
            "description": "Opens the same record in the dashboard, for an alert email or a back-office link."
          },
          "inquiredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the status was entered. Never cleared."
          },
          "reservedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the status was entered. Never cleared."
          },
          "confirmedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the status was entered. Never cleared."
          },
          "cancelledAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the status was entered. Never cleared."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The incremental-sync key: see `updatedSince` on the list."
          }
        },
        "required": [
          "id",
          "status",
          "propertyId",
          "property",
          "guest",
          "checkIn",
          "checkOut",
          "nights",
          "adults",
          "children",
          "infants",
          "pets",
          "pricing",
          "requiresContract",
          "invoicePaid",
          "agreement",
          "dashboardUrl",
          "inquiredAt",
          "reservedAt",
          "confirmedAt",
          "cancelledAt",
          "createdAt",
          "updatedAt"
        ],
        "description": "A booking with its guest, the property’s identity, the money as agreed at booking time and the current agreement if there is one."
      },
      "Signer": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "description": "lessee, lessor, guarantor, witness, or a role of your own."
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "declined"
            ]
          },
          "signingUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only for external envelopes. A Quarters signing link is a secret and never leaves the app."
          },
          "viewedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declinedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declineReason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "role",
          "name",
          "email",
          "status",
          "signingUrl",
          "viewedAt",
          "completedAt",
          "declinedAt",
          "declineReason"
        ],
        "description": "One party to the agreement."
      },
      "Agreement": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "description": "Counts up on every re-issue. Superseded versions are not listed."
          },
          "provider": {
            "type": "string",
            "enum": [
              "quarters",
              "external"
            ],
            "description": "`quarters` for an envelope Quarters issued itself, `external` for one written through this API."
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your stable id for the envelope, as you reported it."
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "completed",
              "declined",
              "failed",
              "superseded",
              "voided"
            ]
          },
          "templateName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Quarters template used; null for an external envelope."
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declinedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "voidedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it failed. Only kept while `status` is `failed`."
          },
          "documentUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The provider-hosted copy of the executed document, as reported."
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "sha256": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "uploadedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            },
            "required": [
              "sha256",
              "uploadedAt"
            ],
            "description": "Set once the executed PDF has been archived: by Quarters for its own envelopes, by you through the document endpoint for yours."
          },
          "signers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signer"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "version",
          "provider",
          "externalId",
          "status",
          "templateName",
          "sentAt",
          "completedAt",
          "declinedAt",
          "voidedAt",
          "error",
          "documentUrl",
          "document",
          "signers",
          "createdAt",
          "updatedAt"
        ],
        "description": "The tenancy agreement on a booking, in its current version."
      },
      "SignerInput": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_-]*$",
            "description": "lessee, lessor, guarantor, witness or your own. Letters, digits, _ and -; matched case-insensitively."
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "declined"
            ]
          },
          "signingUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "pattern": "^https://",
            "maxLength": 2000,
            "description": "Where this party signs. Staff copy it from the booking."
          },
          "viewedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declinedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declineReason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        },
        "required": [
          "role",
          "name",
          "email",
          "status"
        ],
        "description": "One party, as you report it. Signers are matched by role and never removed, so you may send only the party that changed."
      },
      "AgreementInput": {
        "type": "object",
        "properties": {
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your stable id for the envelope (a DocuSeal submission id, say). Required unless `status` is `failed`: a failure before any envelope exists, missing fields or an API outage, annotates the current agreement instead."
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "completed",
              "declined",
              "failed",
              "voided"
            ]
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Defaults to now the first time the status is reached, and is kept afterwards."
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "As `sentAt`."
          },
          "declinedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "As `sentAt`."
          },
          "voidedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "As `sentAt`."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "Why it failed, for staff to read on the booking. Only kept while `status` is `failed`."
          },
          "documentUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "pattern": "^https://",
            "maxLength": 2000,
            "description": "Where the provider hosts the executed document. Shown to staff as a link."
          },
          "signers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignerInput"
            },
            "maxItems": 10,
            "default": [],
            "description": "Up to ten. Matched by role; a party you leave out is left as it was."
          }
        },
        "required": [
          "status"
        ],
        "description": "Your whole current view of the envelope."
      },
      "InquiryInput": {
        "type": "object",
        "properties": {
          "propertyId": {
            "type": "string",
            "description": "Must be one of your published listings, so a typo fails loudly rather than creating an orphan."
          },
          "firstName": {
            "type": "string",
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "maxLength": 50
          },
          "checkIn": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "checkOut": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Must be after checkIn."
          },
          "adults": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20,
            "description": "The tenants picker, one field per age band."
          },
          "children": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "infants": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "pets": {
            "type": "boolean"
          },
          "guestCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Deprecated: folded into `adults`. Send the age bands instead."
          },
          "message": {
            "type": "string",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "maxLength": 10,
            "description": "Which language they were reading."
          },
          "source": {
            "type": "string",
            "maxLength": 50,
            "description": "Where the form lives. Defaults to website."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Anything else your form collects. At most 10 KB serialised. Travels untyped, so the form can evolve without a contract change."
          }
        },
        "required": [
          "lastName",
          "email"
        ],
        "description": "A booking request from your form. Text is trimmed."
      }
    }
  }
}