{
  "openapi": "3.1.0",
  "info": {
    "title": "DOOLAE API",
    "version": "1.0.0",
    "summary": "Properties, rooms, tenants, meters, bills, payments and LINE messages for DOOLAE landlords.",
    "description": "The DOOLAE API lets your software work with a landlord's DOOLAE account: properties, rooms, tenants, meter readings, bills, QR payments and LINE messages — with the landlord's permission.\n\n**Base URL:** `https://api.doolae.online/v1`. Every request and response is JSON (UTF-8). Money is an integer number of satang (`617000` = ฿6,170.00), always with `\"currency\": \"THB\"`. Times are ISO 8601 in UTC.\n\n## Authentication\nSend your secret key as a bearer token: `Authorization: Bearer dl_live_…`. Keys starting with `dl_test_` work in **test mode**, against your application's sandbox account with sample data; they can never reach real landlords. Keys are secrets: never put them in URLs, browser code or source control.\n\n## Accounts\nA live key acts on landlord accounts that **connected your application** (they approve it on DOOLAE's consent page and can disconnect at any time). A key bound to one account needs nothing more; otherwise name the account in the `DOOLAE-Account: acct_…` header. `GET /v1/account` tells you which account a request reaches.\n\n## Scopes\nEach endpoint needs one scope. A key can use a scope only if the key has it **and** the landlord granted it to your application.\n\n| Scope | Allows |\n|---|---|\n| `properties:read` | Read properties: names, addresses, utility rates and the bill due day. |\n| `properties:write` | Create and update properties, and delete properties that have no rooms. |\n| `rooms:read` | Read rooms: room numbers, monthly rent and occupancy. |\n| `rooms:write` | Create rooms and change their rent and status. |\n| `tenants:read` | Read tenants: names, phone numbers, email addresses and whether they use DOOLAE LINE. *(personal or financial data)* |\n| `tenants:write` | Move tenants in and out and update their contact details. *(personal or financial data)* |\n| `meters:read` | Read meters and monthly electricity and water readings. |\n| `meters:write` | Record and correct meter readings (an unpaid bill for the month is recalculated). |\n| `bills:read` | Read bills: amounts, status and due information. |\n| `bills:write` | Create bills from recorded meter readings. |\n| `payments:read` | Read tenants' QR payments for bills: status, amounts and times. *(personal or financial data)* |\n| `payments:write` | Create pay-by-QR links for bills. Never marks anything as paid. |\n| `notifications:read` | Read the status of LINE messages sent to tenants. |\n| `notifications:write` | Send bill notices and messages to tenants on DOOLAE LINE. *(personal or financial data)* |\n| `webhooks:manage` | Create, change and delete this application's webhook endpoints. |\n\n## Rate limits and quotas\nPer developer account (all keys together): a burst size and a sustained rate per second from your plan, plus a monthly number of included requests (live mode). Responses carry `X-RateLimit-*` and `X-Quota-*` headers; over a limit you get `429` with `Retry-After`.\n\n## Pagination, idempotency, errors\nLists are cursor-paginated (`limit` up to 100, `cursor`, `order`) and return `pagination.has_more` and `pagination.next_cursor`. Send an `Idempotency-Key` on writes to retry safely. Errors always look like `{\"error\": {\"code\", \"message\", \"request_id\", \"doc_url\"}}` — branch on `code`.\n\n## Webhooks and events\nRegister HTTPS endpoints to receive signed events (see the **webhooks** section of this document and the webhooks guide). `GET /v1/events` lists the same events for 30 days.\n\n## Versioning\nThis is version 1. Additive changes (new endpoints, new optional parameters, new response fields, new event types) can happen at any time; anything breaking goes into `/v2`, and deprecated parts of `/v1` keep working at least 12 months, announced with `Deprecation` and `Sunset` headers and in the changelog.",
    "termsOfService": "https://developers.doolae.online/terms",
    "contact": {
      "name": "DOOLAE developer support",
      "url": "https://developers.doolae.online/docs"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://api.doolae.online",
      "description": "Live and test mode (the key decides)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "The landlord account a request acts on."
    },
    {
      "name": "Properties",
      "description": "Buildings or places a landlord rents out."
    },
    {
      "name": "Rooms",
      "description": "Rentable rooms in a property."
    },
    {
      "name": "Tenants",
      "description": "The people renting rooms. Personal data: tenants:read."
    },
    {
      "name": "Meters",
      "description": "Electricity and water meters, and monthly readings that bills are calculated from."
    },
    {
      "name": "Bills",
      "description": "Monthly bills: rent plus electricity and water."
    },
    {
      "name": "Payments",
      "description": "Tenants' PromptPay QR payments, confirmed by the payment provider. Read-only."
    },
    {
      "name": "LINE",
      "description": "Messages to tenants on DOOLAE LINE, queued and retried by DOOLAE."
    },
    {
      "name": "Events",
      "description": "What happened in an account; the same objects webhooks deliver."
    },
    {
      "name": "Webhooks",
      "description": "Endpoints that receive signed event deliveries."
    },
    {
      "name": "Test helpers",
      "description": "Test mode only: simulate things that happen outside the API."
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Retrieve the account",
        "description": "The landlord account this key acts on, its plan features and the scopes the key can use here. Works with any valid key.\n\n**Scope:** any valid key",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Account"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/properties": {
      "get": {
        "operationId": "listProperties",
        "summary": "List properties",
        "description": "**Scope:** `properties:read`",
        "tags": [
          "Properties"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Property"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "properties:read"
      },
      "post": {
        "operationId": "createProperty",
        "summary": "Create a property",
        "description": "Counts towards the landlord's DOOLAE plan limit for properties (403 plan_limit_reached when full).\n\n**Scope:** `properties:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Properties"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PropertyCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "properties:write"
      }
    },
    "/v1/properties/{property_id}": {
      "get": {
        "operationId": "getProperty",
        "summary": "Retrieve a property",
        "description": "**Scope:** `properties:read`",
        "tags": [
          "Properties"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "properties:read"
      },
      "patch": {
        "operationId": "updateProperty",
        "summary": "Update a property",
        "description": "Rate changes apply to months recorded after the change; months already recorded keep their rates.\n\n**Scope:** `properties:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Properties"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PropertyUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "properties:write"
      },
      "delete": {
        "operationId": "deleteProperty",
        "summary": "Delete an empty property",
        "description": "Only a property without rooms can be deleted through the API (409 property_not_empty otherwise). A landlord can delete a whole property in DOOLAE.\n\n**Scope:** `properties:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Properties"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No content.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "properties:write"
      }
    },
    "/v1/rooms": {
      "get": {
        "operationId": "listRooms",
        "summary": "List rooms",
        "description": "**Scope:** `rooms:read`",
        "tags": [
          "Rooms"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "property_id",
            "in": "query",
            "required": false,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "vacant",
                "occupied",
                "maintenance"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Room"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "rooms:read"
      }
    },
    "/v1/properties/{property_id}/rooms": {
      "get": {
        "operationId": "listPropertyRooms",
        "summary": "List a property's rooms",
        "description": "**Scope:** `rooms:read`",
        "tags": [
          "Rooms"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "vacant",
                "occupied",
                "maintenance"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Room"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "rooms:read"
      },
      "post": {
        "operationId": "createRoom",
        "summary": "Create a room",
        "description": "Room numbers are unique within a property (409 room_number_taken). Counts towards the landlord's plan limit for rooms.\n\n**Scope:** `rooms:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Rooms"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "property_id",
            "in": "path",
            "required": true,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoomCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Room"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "rooms:write"
      }
    },
    "/v1/rooms/{room_id}": {
      "get": {
        "operationId": "getRoom",
        "summary": "Retrieve a room",
        "description": "**Scope:** `rooms:read`",
        "tags": [
          "Rooms"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Room"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "rooms:read"
      },
      "patch": {
        "operationId": "updateRoom",
        "summary": "Update a room",
        "description": "**Scope:** `rooms:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Rooms"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoomUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Room"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "rooms:write"
      }
    },
    "/v1/tenants": {
      "get": {
        "operationId": "listTenants",
        "summary": "List tenants",
        "description": "**Scope:** `tenants:read`",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "property_id",
            "in": "query",
            "required": false,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "room_id",
            "in": "query",
            "required": false,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tenant"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:read"
      }
    },
    "/v1/tenants/{tenant_id}": {
      "get": {
        "operationId": "getTenant",
        "summary": "Retrieve a tenant",
        "description": "**Scope:** `tenants:read`",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tenant_id",
            "in": "path",
            "required": true,
            "description": "ID of the tenant.",
            "schema": {
              "type": "string",
              "pattern": "^ten_[a-z0-9]{20,40}$",
              "description": "ID of the tenant.",
              "examples": [
                "ten_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Tenant"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:read"
      },
      "patch": {
        "operationId": "updateTenant",
        "summary": "Update a tenant",
        "description": "**Scope:** `tenants:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tenant_id",
            "in": "path",
            "required": true,
            "description": "ID of the tenant.",
            "schema": {
              "type": "string",
              "pattern": "^ten_[a-z0-9]{20,40}$",
              "description": "ID of the tenant.",
              "examples": [
                "ten_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Tenant"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:write"
      },
      "delete": {
        "operationId": "moveOutTenant",
        "summary": "Move a tenant out",
        "description": "Removes the tenant; the room becomes vacant (unless under maintenance). Their bills keep the tenant's name; tenant_id on those bills becomes null.\n\n**Scope:** `tenants:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tenant_id",
            "in": "path",
            "required": true,
            "description": "ID of the tenant.",
            "schema": {
              "type": "string",
              "pattern": "^ten_[a-z0-9]{20,40}$",
              "description": "ID of the tenant.",
              "examples": [
                "ten_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No content.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:write"
      }
    },
    "/v1/rooms/{room_id}/tenant": {
      "get": {
        "operationId": "getRoomTenant",
        "summary": "Retrieve a room's tenant",
        "description": "404 resource_not_found when the room has no tenant.\n\n**Scope:** `tenants:read`",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Tenant"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:read"
      },
      "post": {
        "operationId": "moveInTenant",
        "summary": "Move a tenant in",
        "description": "Adds the tenant of an empty room; the room becomes occupied (unless under maintenance). Never replaces someone living there (409 room_occupied). Counts towards the landlord's plan limit for tenants.\n\n**Scope:** `tenants:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Tenants"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Tenant"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "tenants:write"
      }
    },
    "/v1/rooms/{room_id}/meters": {
      "get": {
        "operationId": "listRoomMeters",
        "summary": "List a room's meters",
        "description": "Every room has an electricity meter and a water meter.\n\n**Scope:** `meters:read`",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Meter"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:read"
      }
    },
    "/v1/meters/{meter_id}": {
      "get": {
        "operationId": "getMeter",
        "summary": "Retrieve a meter",
        "description": "**Scope:** `meters:read`",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "meter_id",
            "in": "path",
            "required": true,
            "description": "ID of the meter: one electricity (mtr_e_…) and one water (mtr_w_…) meter per room.",
            "schema": {
              "type": "string",
              "pattern": "^mtr_(e|w)_[a-z0-9]{20,40}$",
              "description": "ID of the meter: one electricity (mtr_e_…) and one water (mtr_w_…) meter per room.",
              "examples": [
                "mtr_e_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Meter"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:read"
      }
    },
    "/v1/meter-readings": {
      "get": {
        "operationId": "listMeterReadings",
        "summary": "List meter readings",
        "description": "**Scope:** `meters:read`",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "property_id",
            "in": "query",
            "required": false,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "room_id",
            "in": "query",
            "required": false,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "from_month",
            "in": "query",
            "required": false,
            "description": "Only months from this one (inclusive).",
            "schema": {
              "description": "Only months from this one (inclusive).",
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "to_month",
            "in": "query",
            "required": false,
            "description": "Only months up to this one (inclusive).",
            "schema": {
              "description": "Only months up to this one (inclusive).",
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MeterReading"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:read"
      },
      "post": {
        "operationId": "createMeterReading",
        "summary": "Record a meter reading",
        "description": "Records one room's electricity and water for a month. `previous` defaults to last month's `current` (0 for a new meter). Refused when current < previous (422 invalid_reading), when the month is already recorded (409 resource_exists — correct it with PATCH), or when the month's bill is paid (409 reading_locked). An unpaid bill for the month is recalculated.\n\n**Scope:** `meters:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MeterReadingCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MeterReading"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:write"
      }
    },
    "/v1/rooms/{room_id}/meter-readings": {
      "get": {
        "operationId": "listRoomMeterReadings",
        "summary": "List a room's meter readings",
        "description": "**Scope:** `meters:read`",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "from_month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "to_month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MeterReading"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:read"
      },
      "post": {
        "operationId": "createRoomMeterReading",
        "summary": "Record a room's meter reading",
        "description": "Same as POST /v1/meter-readings, with the room in the path.\n\n**Scope:** `meters:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "room_id",
            "in": "path",
            "required": true,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoomMeterReadingCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MeterReading"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:write"
      }
    },
    "/v1/meter-readings/{reading_id}": {
      "get": {
        "operationId": "getMeterReading",
        "summary": "Retrieve a meter reading",
        "description": "**Scope:** `meters:read`",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "reading_id",
            "in": "path",
            "required": true,
            "description": "ID of the meter reading.",
            "schema": {
              "type": "string",
              "pattern": "^rdg_[a-z0-9]{20,40}$",
              "description": "ID of the meter reading.",
              "examples": [
                "rdg_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MeterReading"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:read"
      },
      "patch": {
        "operationId": "updateMeterReading",
        "summary": "Correct a meter reading",
        "description": "Changes recorded values. An unpaid bill for the month is recalculated; a paid month is locked (409 reading_locked).\n\n**Scope:** `meters:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Meters"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "reading_id",
            "in": "path",
            "required": true,
            "description": "ID of the meter reading.",
            "schema": {
              "type": "string",
              "pattern": "^rdg_[a-z0-9]{20,40}$",
              "description": "ID of the meter reading.",
              "examples": [
                "rdg_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MeterReadingUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MeterReading"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "meters:write"
      }
    },
    "/v1/bills": {
      "get": {
        "operationId": "listBills",
        "summary": "List bills",
        "description": "**Scope:** `bills:read`",
        "tags": [
          "Bills"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "property_id",
            "in": "query",
            "required": false,
            "description": "ID of the property.",
            "schema": {
              "type": "string",
              "pattern": "^prop_[a-z0-9]{20,40}$",
              "description": "ID of the property.",
              "examples": [
                "prop_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "room_id",
            "in": "query",
            "required": false,
            "description": "ID of the room.",
            "schema": {
              "type": "string",
              "pattern": "^room_[a-z0-9]{20,40}$",
              "description": "ID of the room.",
              "examples": [
                "room_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "tenant_id",
            "in": "query",
            "required": false,
            "description": "ID of the tenant.",
            "schema": {
              "type": "string",
              "pattern": "^ten_[a-z0-9]{20,40}$",
              "description": "ID of the tenant.",
              "examples": [
                "ten_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "issued",
                "paid",
                "overdue"
              ]
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "from_month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "to_month",
            "in": "query",
            "required": false,
            "description": "Billing month, YYYY-MM.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, YYYY-MM.",
              "examples": [
                "2026-10"
              ]
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "description": "Only items created at or after this time.",
            "schema": {
              "description": "Only items created at or after this time.",
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "required": false,
            "description": "Only items created before this time.",
            "schema": {
              "description": "Only items created before this time.",
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Bill"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "bills:read"
      },
      "post": {
        "operationId": "createBill",
        "summary": "Create a bill",
        "description": "Creates a room's bill for a month from its recorded meter reading (rent + electricity + water, calculated by DOOLAE). The room needs a tenant (409 room_has_no_tenant) and a reading for the month (409 reading_required). One bill per room and month (409 resource_exists). No LINE message is sent — use POST /v1/notifications for that.\n\n**Scope:** `bills:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Bills"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Bill"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "bills:write"
      }
    },
    "/v1/bills/{bill_id}": {
      "get": {
        "operationId": "getBill",
        "summary": "Retrieve a bill",
        "description": "**Scope:** `bills:read`",
        "tags": [
          "Bills"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "bill_id",
            "in": "path",
            "required": true,
            "description": "ID of the bill.",
            "schema": {
              "type": "string",
              "pattern": "^bill_[a-z0-9]{20,40}$",
              "description": "ID of the bill.",
              "examples": [
                "bill_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Bill"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "bills:read"
      }
    },
    "/v1/bills/{bill_id}/payments": {
      "get": {
        "operationId": "listBillPayments",
        "summary": "List a bill's payments",
        "description": "**Scope:** `payments:read`",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "bill_id",
            "in": "path",
            "required": true,
            "description": "ID of the bill.",
            "schema": {
              "type": "string",
              "pattern": "^bill_[a-z0-9]{20,40}$",
              "description": "ID of the bill.",
              "examples": [
                "bill_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "payments:read"
      }
    },
    "/v1/bills/{bill_id}/payment-link": {
      "post": {
        "operationId": "createPaymentLink",
        "summary": "Get a bill's pay-by-QR link",
        "description": "Returns the link a tenant opens to pay the bill by PromptPay QR (QR Code พร้อมเพย์), creating it if needed. Requires the landlord's QR Code พร้อมเพย์ to be set up and active, on the Starter plan or higher (409 bill_not_payable otherwise). The bill becomes paid only when the payment provider confirms the transfer. In test mode the link opens a sandbox page where you can simulate the payment.\n\n**Scope:** `payments:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "bill_id",
            "in": "path",
            "required": true,
            "description": "ID of the bill.",
            "schema": {
              "type": "string",
              "pattern": "^bill_[a-z0-9]{20,40}$",
              "description": "ID of the bill.",
              "examples": [
                "bill_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentLink"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "payments:write"
      }
    },
    "/v1/payments": {
      "get": {
        "operationId": "listPayments",
        "summary": "List payments",
        "description": "**Scope:** `payments:read`",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "bill_id",
            "in": "query",
            "required": false,
            "description": "ID of the bill.",
            "schema": {
              "type": "string",
              "pattern": "^bill_[a-z0-9]{20,40}$",
              "description": "ID of the bill.",
              "examples": [
                "bill_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "paid",
                "expired",
                "canceled"
              ]
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "description": "Only items created at or after this time.",
            "schema": {
              "description": "Only items created at or after this time.",
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "required": false,
            "description": "Only items created before this time.",
            "schema": {
              "description": "Only items created before this time.",
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "payments:read"
      }
    },
    "/v1/payments/{payment_id}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Retrieve a payment",
        "description": "**Scope:** `payments:read`",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "payment_id",
            "in": "path",
            "required": true,
            "description": "ID of the payment.",
            "schema": {
              "type": "string",
              "pattern": "^pay_[a-z0-9]{20,40}$",
              "description": "ID of the payment.",
              "examples": [
                "pay_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Payment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "payments:read"
      }
    },
    "/v1/notifications": {
      "post": {
        "operationId": "createNotification",
        "summary": "Send a bill notice on LINE",
        "description": "Queues a bill notice (new bill, reminder or overdue) to the bill's tenant on DOOLAE LINE, with a payment QR when the landlord has QR payments on. Returns at once with status queued; DOOLAE delivers within seconds and retries if LINE is unavailable — follow it with GET /v1/notifications/{id} or the notification.sent / notification.failed webhooks. Limited to 60 sends per minute per account (429 sending_limit).\n\n**Scope:** `notifications:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "LINE"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationCreate"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: queued for delivery.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Notification"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "notifications:write"
      },
      "get": {
        "operationId": "listNotifications",
        "summary": "List LINE messages",
        "description": "Messages sent to this account's tenants — through the API and from DOOLAE itself (bills, reminders, payment receipts).\n\n**Scope:** `notifications:read`",
        "tags": [
          "LINE"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "tenant_id",
            "in": "query",
            "required": false,
            "description": "ID of the tenant.",
            "schema": {
              "type": "string",
              "pattern": "^ten_[a-z0-9]{20,40}$",
              "description": "ID of the tenant.",
              "examples": [
                "ten_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "bill_id",
            "in": "query",
            "required": false,
            "description": "ID of the bill.",
            "schema": {
              "type": "string",
              "pattern": "^bill_[a-z0-9]{20,40}$",
              "description": "ID of the bill.",
              "examples": [
                "bill_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "sent",
                "failed"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "notifications:read"
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "summary": "Send a message on LINE",
        "description": "Queues a text message to one tenant, the tenant of a room, or every tenant of a property, on DOOLAE LINE (the message starts with the property's name). Returns one notification per tenant. Tenants who haven't connected DOOLAE LINE, or turned messages off, get a failed notification with the reason.\n\n**Scope:** `notifications:write`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "LINE"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreate"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: queued for delivery.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "notifications:write"
      }
    },
    "/v1/notifications/{notification_id}": {
      "get": {
        "operationId": "getNotification",
        "summary": "Retrieve a LINE message",
        "description": "**Scope:** `notifications:read`",
        "tags": [
          "LINE"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "notification_id",
            "in": "path",
            "required": true,
            "description": "ID of the notification.",
            "schema": {
              "type": "string",
              "pattern": "^ntf_[a-z0-9]{20,40}$",
              "description": "ID of the notification.",
              "examples": [
                "ntf_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Notification"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "notifications:read"
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "List events",
        "description": "Events in this account from the last 30 days, newest first — the same objects webhooks deliver. Only types this key can read (e.g. bill.* needs bills:read). Use it to catch up after downtime.\n\n**Scope:** any valid key",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "property.created",
                "property.updated",
                "property.deleted",
                "room.created",
                "room.updated",
                "room.deleted",
                "tenant.created",
                "tenant.updated",
                "tenant.deleted",
                "tenant.line_registered",
                "meter.reading.created",
                "meter.reading.updated",
                "bill.created",
                "bill.updated",
                "bill.overdue",
                "bill.deleted",
                "payment.pending",
                "payment.paid",
                "payment.failed",
                "notification.sent",
                "notification.failed"
              ]
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/events/{event_id}": {
      "get": {
        "operationId": "getEvent",
        "summary": "Retrieve an event",
        "description": "**Scope:** any valid key",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event_id",
            "in": "path",
            "required": true,
            "description": "ID of the event.",
            "schema": {
              "type": "string",
              "pattern": "^evt_[a-z0-9]{20,40}$",
              "description": "ID of the event.",
              "examples": [
                "evt_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Event"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/webhook-endpoints": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints",
        "description": "This application's endpoints in the key's mode (test keys see test endpoints).\n\n**Scope:** `webhooks:manage`",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1–100 (default 20).",
            "schema": {
              "default": 20,
              "description": "Items per page, 1–100 (default 20).",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "description": "`next_cursor` from the previous page.",
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "By creation time: newest first (desc, default) or oldest first (asc).",
            "schema": {
              "default": "desc",
              "description": "By creation time: newest first (desc, default) or oldest first (asc).",
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "summary": "Create a webhook endpoint",
        "description": "HTTPS URLs on the public internet only. The response includes the signing secret (whsec_…) — store it; it isn't returned again except after a rotation.\n\n**Scope:** `webhooks:manage`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/v1/webhook-endpoints/{endpoint_id}": {
      "get": {
        "operationId": "getWebhookEndpoint",
        "summary": "Retrieve a webhook endpoint",
        "description": "**Scope:** `webhooks:manage`",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "ID of the webhook endpoint.",
            "schema": {
              "type": "string",
              "pattern": "^whep_[a-z0-9]{20,40}$",
              "description": "ID of the webhook endpoint.",
              "examples": [
                "whep_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      },
      "patch": {
        "operationId": "updateWebhookEndpoint",
        "summary": "Update a webhook endpoint",
        "description": "**Scope:** `webhooks:manage`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "ID of the webhook endpoint.",
            "schema": {
              "type": "string",
              "pattern": "^whep_[a-z0-9]{20,40}$",
              "description": "ID of the webhook endpoint.",
              "examples": [
                "whep_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      },
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a webhook endpoint",
        "description": "**Scope:** `webhooks:manage`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "ID of the webhook endpoint.",
            "schema": {
              "type": "string",
              "pattern": "^whep_[a-z0-9]{20,40}$",
              "description": "ID of the webhook endpoint.",
              "examples": [
                "whep_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No content.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/v1/webhook-endpoints/{endpoint_id}/rotate-secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "Rotate an endpoint's signing secret",
        "description": "Returns the new secret. For 24 hours deliveries carry a second signature made with the previous secret, so you can switch without missing events.\n\n**Scope:** `webhooks:manage`\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "ID of the webhook endpoint.",
            "schema": {
              "type": "string",
              "pattern": "^whep_[a-z0-9]{20,40}$",
              "description": "ID of the webhook endpoint.",
              "examples": [
                "whep_cm2x8k1qz0001a8b3c4d5e6f7"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "webhooks:manage"
      }
    },
    "/v1/test-helpers/payments": {
      "post": {
        "operationId": "simulatePayment",
        "summary": "Simulate a payment (test mode)",
        "description": "Test keys only (403 test_mode_only with a live key). Simulates a tenant paying a sandbox bill by QR: sends payment.pending, then payment.paid (the bill becomes paid, a payment-received LINE message is recorded) or payment.failed. Nothing reaches a payment provider or LINE.\n\n**Scope:** `payments:write`\n\n**Test mode only.**\n\nSupports `Idempotency-Key`.",
        "tags": [
          "Test helpers"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/DoolaeAccount"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestPaymentCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/RequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Payment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-required-scope": "payments:write",
        "x-test-mode-only": true
      }
    }
  },
  "webhooks": {
    "property.created": {
      "post": {
        "summary": "property.created",
        "description": "A property was created. `data.object` is the property as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "property.updated": {
      "post": {
        "summary": "property.updated",
        "description": "A property's details or rates changed. `data.object` is the property as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "property.deleted": {
      "post": {
        "summary": "property.deleted",
        "description": "A property was deleted (with its rooms, readings and bills). `data.object` is the property as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "room.created": {
      "post": {
        "summary": "room.created",
        "description": "A room was created (by the landlord, an import, the API or a tenant registering through DOOLAE LINE). `data.object` is the room as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "room.updated": {
      "post": {
        "summary": "room.updated",
        "description": "A room's number, rent or status changed. `data.object` is the room as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "room.deleted": {
      "post": {
        "summary": "room.deleted",
        "description": "A room was deleted. `data.object` is the room as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "tenant.created": {
      "post": {
        "summary": "tenant.created",
        "description": "A tenant moved in. `data.object` is the tenant as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "tenant.updated": {
      "post": {
        "summary": "tenant.updated",
        "description": "A tenant's details or LINE notification setting changed. `data.object` is the tenant as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "tenant.deleted": {
      "post": {
        "summary": "tenant.deleted",
        "description": "A tenant moved out. `data.object` is the tenant as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "tenant.line_registered": {
      "post": {
        "summary": "tenant.line_registered",
        "description": "A tenant connected their LINE account to DOOLAE LINE. `data.object` is the tenant as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "meter.reading.created": {
      "post": {
        "summary": "meter.reading.created",
        "description": "A month's meter reading was recorded for a room. `data.object` is the meter reading as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "meter.reading.updated": {
      "post": {
        "summary": "meter.reading.updated",
        "description": "A recorded meter reading was corrected. `data.object` is the meter reading as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "bill.created": {
      "post": {
        "summary": "bill.created",
        "description": "A bill was created. `data.object` is the bill as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "bill.updated": {
      "post": {
        "summary": "bill.updated",
        "description": "A bill changed: amounts recalculated, status changed (including paid). `data.object` is the bill as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "bill.overdue": {
      "post": {
        "summary": "bill.overdue",
        "description": "A bill was marked overdue. `data.object` is the bill as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "bill.deleted": {
      "post": {
        "summary": "bill.deleted",
        "description": "An unpaid bill was deleted. `data.object` is the bill as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "payment.pending": {
      "post": {
        "summary": "payment.pending",
        "description": "A tenant opened a QR payment for a bill. `data.object` is the payment as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "payment.paid": {
      "post": {
        "summary": "payment.paid",
        "description": "A QR payment was confirmed by the payment provider. `data.object` is the payment as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "payment.failed": {
      "post": {
        "summary": "payment.failed",
        "description": "A QR payment ended without being paid (expired or cancelled). `data.object` is the payment as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "notification.sent": {
      "post": {
        "summary": "notification.sent",
        "description": "A LINE message reached the tenant. `data.object` is the notification as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    },
    "notification.failed": {
      "post": {
        "summary": "notification.failed",
        "description": "A LINE message could not be delivered (DOOLAE stopped retrying). `data.object` is the notification as the API returns it.",
        "parameters": [
          {
            "name": "X-DOOLAE-Event",
            "in": "header",
            "required": true,
            "description": "The event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Event-ID",
            "in": "header",
            "required": true,
            "description": "Unique event ID (evt_…): de-duplicate on it — a delivery can arrive more than once.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Delivery-ID",
            "in": "header",
            "required": true,
            "description": "This delivery (whdl_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time (seconds) the delivery was signed. Refuse ones older than 5 minutes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Signature",
            "in": "header",
            "required": true,
            "description": "sha256=<hex HMAC-SHA256 of \"<timestamp>.<raw body>\" with your endpoint's secret>. During a secret rotation a second sha256=… follows, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-DOOLAE-Attempt",
            "in": "header",
            "required": true,
            "description": "1 for the first try, then 2, 3, …",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx within 10 seconds means received. Anything else (or no answer) is retried: after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code (see the errors guide).",
                "examples": [
                  "resource_not_found"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. May change; branch on `code`.",
                "examples": [
                  "No such bill."
                ]
              },
              "request_id": {
                "type": "string",
                "description": "Quote this ID when contacting support.",
                "examples": [
                  "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718"
                ]
              },
              "param": {
                "description": "The request parameter the error is about, when there is one.",
                "examples": [
                  "month"
                ],
                "type": "string"
              },
              "doc_url": {
                "type": "string",
                "description": "Documentation for this error code."
              }
            },
            "required": [
              "code",
              "message",
              "request_id",
              "doc_url"
            ]
          }
        },
        "required": [
          "error"
        ],
        "description": "Every error response has this shape."
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "has_more": {
            "type": "boolean",
            "description": "Whether another page follows."
          },
          "next_cursor": {
            "description": "Pass as `cursor` to get the next page; null on the last page.",
            "examples": [
              "eyJ0IjoxNzkwMDAwMDAwMDAwLCJpIjoiY20y…"
            ],
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "has_more",
          "next_cursor"
        ]
      },
      "Account": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "account"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "test: the application's sandbox account with sample data."
          },
          "name": {
            "type": "string",
            "description": "The landlord's display name (\"Sandbox …\" in test mode).",
            "examples": [
              "Somchai Rentals"
            ]
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "starter",
              "pro",
              "business"
            ],
            "description": "The landlord's DOOLAE plan (limits how many properties, rooms and tenants can exist)."
          },
          "features": {
            "type": "object",
            "properties": {
              "line": {
                "type": "boolean",
                "description": "Tenants can receive messages on DOOLAE LINE."
              },
              "qr_payments": {
                "type": "boolean",
                "description": "QR payments with automatic payment verification (QR Code พร้อมเพย์) are included in the plan: true on Starter and up, false on Free. Whether they are set up yet shows when you request a pay link (409 bill_not_payable)."
              }
            },
            "required": [
              "line",
              "qr_payments"
            ]
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "properties:read",
                "properties:write",
                "rooms:read",
                "rooms:write",
                "tenants:read",
                "tenants:write",
                "meters:read",
                "meters:write",
                "bills:read",
                "bills:write",
                "payments:read",
                "payments:write",
                "notifications:read",
                "notifications:write",
                "webhooks:manage"
              ]
            },
            "description": "Scopes this key can use on this account (key scopes granted by the landlord)."
          },
          "connected_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "mode",
          "name",
          "plan",
          "features",
          "scopes",
          "connected_at"
        ],
        "description": "The landlord account this request acts on."
      },
      "Property": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "property"
          },
          "name": {
            "type": "string",
            "examples": [
              "Baan Suan Residence"
            ]
          },
          "address": {
            "examples": [
              "99 Soi Sukhumvit 50, Phra Khanong, Khlong Toei, Bangkok 10260"
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "electricity_rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Electricity price per kWh. In satang (1 THB = 100 satang).",
            "examples": [
              800
            ]
          },
          "water_billing": {
            "type": "string",
            "enum": [
              "metered",
              "flat"
            ],
            "description": "metered: water is charged per m³ at water_rate. flat: every room pays water_flat_amount a month."
          },
          "water_rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Water price per m³ (metered water). In satang (1 THB = 100 satang).",
            "examples": [
              1800
            ]
          },
          "water_flat_amount": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Monthly water charge per room (flat water); null when water is metered. In satang (1 THB = 100 satang).",
                "examples": [
                  15000
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "due_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28,
            "description": "Day of the month bills are due.",
            "examples": [
              5
            ]
          },
          "room_count": {
            "type": "integer",
            "minimum": 0,
            "examples": [
              24
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "address",
          "electricity_rate",
          "water_billing",
          "water_rate",
          "water_flat_amount",
          "due_day",
          "room_count",
          "created_at",
          "updated_at"
        ],
        "description": "A building or place the landlord rents out."
      },
      "PropertyCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "examples": [
              "Baan Suan Residence"
            ]
          },
          "address": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 300
              },
              {
                "type": "null"
              }
            ]
          },
          "electricity_rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Electricity price per kWh. In satang (1 THB = 100 satang).",
            "examples": [
              800
            ]
          },
          "water_billing": {
            "default": "metered",
            "type": "string",
            "enum": [
              "metered",
              "flat"
            ]
          },
          "water_rate": {
            "default": 0,
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Water price per m³. In satang (1 THB = 100 satang).",
            "examples": [
              1800
            ]
          },
          "water_flat_amount": {
            "default": 0,
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Monthly water charge per room when water_billing is flat. In satang (1 THB = 100 satang).",
            "examples": [
              15000
            ]
          },
          "due_day": {
            "default": 5,
            "type": "integer",
            "minimum": 1,
            "maximum": 28
          }
        },
        "required": [
          "name",
          "electricity_rate"
        ],
        "additionalProperties": false
      },
      "PropertyUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "examples": [
              "Baan Suan Residence"
            ]
          },
          "address": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 300
              },
              {
                "type": "null"
              }
            ]
          },
          "electricity_rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Electricity price per kWh. In satang (1 THB = 100 satang).",
            "examples": [
              800
            ]
          },
          "water_billing": {
            "type": "string",
            "enum": [
              "metered",
              "flat"
            ]
          },
          "water_rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Water price per m³. In satang (1 THB = 100 satang).",
            "examples": [
              1800
            ]
          },
          "water_flat_amount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Monthly water charge per room when water_billing is flat. In satang (1 THB = 100 satang).",
            "examples": [
              15000
            ]
          },
          "due_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28
          }
        },
        "additionalProperties": false
      },
      "Room": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "room"
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "number": {
            "type": "string",
            "examples": [
              "101"
            ]
          },
          "monthly_rent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Monthly rent. In satang (1 THB = 100 satang).",
            "examples": [
              450000
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "vacant",
              "occupied",
              "maintenance"
            ]
          },
          "tenant_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^ten_[a-z0-9]{20,40}$",
                "description": "ID of the tenant living here.",
                "examples": [
                  "ten_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "property_id",
          "number",
          "monthly_rent",
          "status",
          "tenant_id",
          "created_at",
          "updated_at"
        ]
      },
      "RoomCreate": {
        "type": "object",
        "properties": {
          "number": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30,
            "pattern": "^[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N} ._/#-]*$",
            "description": "Room number, unique within the property.",
            "examples": [
              "101"
            ]
          },
          "monthly_rent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Monthly rent. In satang (1 THB = 100 satang).",
            "examples": [
              450000
            ]
          },
          "status": {
            "default": "vacant",
            "description": "A room becomes occupied when a tenant moves in.",
            "type": "string",
            "enum": [
              "vacant",
              "maintenance"
            ]
          }
        },
        "required": [
          "number",
          "monthly_rent"
        ],
        "additionalProperties": false
      },
      "RoomUpdate": {
        "type": "object",
        "properties": {
          "number": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30,
            "pattern": "^[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N} ._/#-]*$",
            "description": "Room number, unique within the property.",
            "examples": [
              "101"
            ]
          },
          "monthly_rent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Monthly rent. In satang (1 THB = 100 satang).",
            "examples": [
              450000
            ]
          },
          "status": {
            "description": "A room with a tenant can't be set to vacant (it stays occupied).",
            "type": "string",
            "enum": [
              "vacant",
              "occupied",
              "maintenance"
            ]
          }
        },
        "additionalProperties": false
      },
      "Tenant": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "tenant"
          },
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Malee Jaidee"
            ]
          },
          "phone": {
            "examples": [
              "081 234 5678"
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "examples": [
              "malee@example.com"
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "line": {
            "type": "object",
            "properties": {
              "connected": {
                "type": "boolean",
                "description": "The tenant uses DOOLAE LINE (receives bills and messages there)."
              },
              "notifications_enabled": {
                "description": "Whether the tenant receives messages; null when not connected.",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "connected_at": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 time (UTC).",
                    "examples": [
                      "2026-10-07T03:15:00.000Z"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "connected",
              "notifications_enabled",
              "connected_at"
            ],
            "description": "DOOLAE LINE status. LINE account identifiers are never exposed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "room_id",
          "property_id",
          "name",
          "phone",
          "email",
          "line",
          "created_at",
          "updated_at"
        ],
        "description": "The person renting a room (personal data: requires tenants:read)."
      },
      "TenantCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "phone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 30,
                "pattern": "^[0-9+\\-\\s()]{6,30}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "format": "email",
                "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "TenantUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "phone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 30,
                "pattern": "^[0-9+\\-\\s()]{6,30}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "format": "email",
                "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "Meter": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "meter"
          },
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "electricity",
              "water"
            ]
          },
          "unit": {
            "type": "string",
            "enum": [
              "kWh",
              "m3"
            ]
          },
          "billing": {
            "type": "string",
            "enum": [
              "metered",
              "flat"
            ],
            "description": "Electricity is always metered; water follows the property's setting."
          },
          "rate": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Current price per unit (from the property). In satang (1 THB = 100 satang).",
            "examples": [
              800
            ]
          },
          "flat_amount": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Monthly flat charge (flat water only). In satang (1 THB = 100 satang).",
                "examples": [
                  15000
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "last_reading": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "reading_id": {
                    "type": "string",
                    "pattern": "^rdg_[a-z0-9]{20,40}$",
                    "description": "ID of the meter reading.",
                    "examples": [
                      "rdg_cm2x8k1qz0001a8b3c4d5e6f7"
                    ]
                  },
                  "month": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                    "description": "Billing month, YYYY-MM.",
                    "examples": [
                      "2026-10"
                    ]
                  },
                  "value": {
                    "type": "number",
                    "description": "Meter value at the end of that month.",
                    "examples": [
                      1290.5
                    ]
                  }
                },
                "required": [
                  "reading_id",
                  "month",
                  "value"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "object",
          "room_id",
          "property_id",
          "type",
          "unit",
          "billing",
          "rate",
          "flat_amount",
          "last_reading"
        ],
        "description": "A room's electricity or water meter."
      },
      "MeterReading": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "meter_reading"
          },
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Billing month, YYYY-MM.",
            "examples": [
              "2026-10"
            ]
          },
          "electricity": {
            "type": "object",
            "properties": {
              "previous": {
                "type": "number",
                "description": "Meter value at the start of the month (kWh).",
                "examples": [
                  1200.5
                ]
              },
              "current": {
                "type": "number",
                "description": "Meter value at the end of the month (kWh).",
                "examples": [
                  1290.5
                ]
              },
              "usage": {
                "type": "number",
                "description": "current − previous (kWh).",
                "examples": [
                  90
                ]
              },
              "rate": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Price per kWh, fixed when the month was first recorded. In satang (1 THB = 100 satang).",
                "examples": [
                  800
                ]
              }
            },
            "required": [
              "previous",
              "current",
              "usage",
              "rate"
            ]
          },
          "water": {
            "type": "object",
            "properties": {
              "billing": {
                "type": "string",
                "enum": [
                  "metered",
                  "flat"
                ]
              },
              "previous": {
                "type": "number",
                "description": "Meter value at the start of the month (m³).",
                "examples": [
                  1200.5
                ]
              },
              "current": {
                "type": "number",
                "description": "Meter value at the end of the month (m³).",
                "examples": [
                  1290.5
                ]
              },
              "usage": {
                "type": "number",
                "description": "current − previous (m³).",
                "examples": [
                  90
                ]
              },
              "rate": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Price per m³, fixed when the month was first recorded. In satang (1 THB = 100 satang).",
                "examples": [
                  800
                ]
              },
              "flat_amount": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000000000,
                    "description": "Monthly flat charge when billing is flat. In satang (1 THB = 100 satang).",
                    "examples": [
                      15000
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "billing",
              "previous",
              "current",
              "usage",
              "rate",
              "flat_amount"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "api",
              "meter"
            ],
            "description": "Who recorded it: the landlord (manual), an API integration (api) or a meter gateway (meter)."
          },
          "bill_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^bill_[a-z0-9]{20,40}$",
                "description": "ID of the bill for this room and month.",
                "examples": [
                  "bill_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "room_id",
          "property_id",
          "month",
          "electricity",
          "water",
          "source",
          "bill_id",
          "created_at",
          "updated_at"
        ],
        "description": "One room's electricity and water readings for one month."
      },
      "MeterReadingCreate": {
        "type": "object",
        "properties": {
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Billing month, YYYY-MM.",
            "examples": [
              "2026-10"
            ]
          },
          "electricity": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the end of the month (kWh).",
                "examples": [
                  1290.5
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the start of the month (kWh). Defaults to the previous month's current value (0 for a new meter).",
                "examples": [
                  1200.5
                ]
              }
            },
            "required": [
              "current"
            ],
            "additionalProperties": false
          },
          "water": {
            "description": "Required unless the room's water is billed at a flat rate.",
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the end of the month (m³).",
                "examples": [
                  312
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the start of the month (m³). Defaults to the previous month's current value (0 for a new meter).",
                "examples": [
                  222
                ]
              }
            },
            "required": [
              "current"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "room_id",
          "month",
          "electricity"
        ],
        "additionalProperties": false
      },
      "RoomMeterReadingCreate": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Billing month, YYYY-MM.",
            "examples": [
              "2026-10"
            ]
          },
          "electricity": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the end of the month (kWh).",
                "examples": [
                  1290.5
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the start of the month (kWh). Defaults to the previous month's current value (0 for a new meter).",
                "examples": [
                  1200.5
                ]
              }
            },
            "required": [
              "current"
            ],
            "additionalProperties": false
          },
          "water": {
            "description": "Required unless the room's water is billed at a flat rate.",
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the end of the month (m³).",
                "examples": [
                  312
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "Meter value at the start of the month (m³). Defaults to the previous month's current value (0 for a new meter).",
                "examples": [
                  222
                ]
              }
            },
            "required": [
              "current"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "month",
          "electricity"
        ],
        "additionalProperties": false
      },
      "MeterReadingUpdate": {
        "type": "object",
        "properties": {
          "electricity": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "kWh",
                "examples": [
                  1290.5
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "kWh",
                "examples": [
                  1200.5
                ]
              }
            },
            "additionalProperties": false
          },
          "water": {
            "type": "object",
            "properties": {
              "current": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "m³",
                "examples": [
                  312
                ]
              },
              "previous": {
                "type": "number",
                "minimum": 0,
                "maximum": 99999999.99,
                "multipleOf": 0.01,
                "description": "m³",
                "examples": [
                  300
                ]
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "Bill": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "bill"
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "tenant_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^ten_[a-z0-9]{20,40}$",
                "description": "ID of the tenant billed.",
                "examples": [
                  "ten_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Billing month, YYYY-MM.",
            "examples": [
              "2026-10"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "issued",
              "paid",
              "overdue"
            ],
            "description": "draft → issued → paid; issued ↔ overdue. Paid comes only from a verified payment or the landlord."
          },
          "currency": {
            "type": "string",
            "const": "THB",
            "description": "Always THB."
          },
          "rent_amount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Rent. In satang (1 THB = 100 satang).",
            "examples": [
              450000
            ]
          },
          "electricity": {
            "type": "object",
            "properties": {
              "usage": {
                "type": "number",
                "description": "kWh.",
                "examples": [
                  90
                ]
              },
              "rate": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Price per kWh. In satang (1 THB = 100 satang).",
                "examples": [
                  800
                ]
              },
              "amount": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Electricity charge. In satang (1 THB = 100 satang).",
                "examples": [
                  72000
                ]
              }
            },
            "required": [
              "usage",
              "rate",
              "amount"
            ]
          },
          "water": {
            "type": "object",
            "properties": {
              "billing": {
                "type": "string",
                "enum": [
                  "metered",
                  "flat"
                ]
              },
              "usage": {
                "type": "number",
                "description": "m³ (0 for flat water).",
                "examples": [
                  12
                ]
              },
              "rate": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Price per m³ (0 for flat water). In satang (1 THB = 100 satang).",
                "examples": [
                  1800
                ]
              },
              "amount": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Water charge. In satang (1 THB = 100 satang).",
                "examples": [
                  21600
                ]
              }
            },
            "required": [
              "billing",
              "usage",
              "rate",
              "amount"
            ]
          },
          "total_amount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Total due. In satang (1 THB = 100 satang).",
            "examples": [
              543600
            ]
          },
          "paid_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 time (UTC).",
                "examples": [
                  "2026-10-07T03:15:00.000Z"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "paid_via": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "qr",
                  "manual"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "qr: a verified PromptPay QR payment. manual: marked paid by the landlord."
          },
          "payment_url": {
            "description": "The tenant's pay-by-QR link, once one has been created (see POST /v1/bills/{bill_id}/payment-link).",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "property_id",
          "room_id",
          "tenant_id",
          "month",
          "status",
          "currency",
          "rent_amount",
          "electricity",
          "water",
          "total_amount",
          "paid_at",
          "paid_via",
          "payment_url",
          "created_at",
          "updated_at"
        ],
        "description": "A room's bill for one month: rent plus electricity and water."
      },
      "BillCreate": {
        "type": "object",
        "properties": {
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Billing month, YYYY-MM.",
            "examples": [
              "2026-10"
            ]
          }
        },
        "required": [
          "room_id",
          "month"
        ],
        "additionalProperties": false,
        "description": "Creates the room's bill for the month from its recorded meter reading."
      },
      "Payment": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "payment"
          },
          "bill_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^bill_[a-z0-9]{20,40}$",
                "description": "ID of the bill.",
                "examples": [
                  "bill_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "property_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^prop_[a-z0-9]{20,40}$",
                "description": "ID of the property.",
                "examples": [
                  "prop_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "room_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^room_[a-z0-9]{20,40}$",
                "description": "ID of the room.",
                "examples": [
                  "room_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "paid",
              "expired",
              "canceled"
            ],
            "description": "pending → paid | expired | canceled; an expired payment can still become paid if the transfer arrives late."
          },
          "method": {
            "type": "string",
            "const": "promptpay_qr"
          },
          "amount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000000000,
            "description": "Amount requested (the bill's total when the QR was opened). In satang (1 THB = 100 satang).",
            "examples": [
              543600
            ]
          },
          "amount_paid": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000000000,
                "description": "Amount received, when paid (the provider may add a few satang to make the amount unique). In satang (1 THB = 100 satang).",
                "examples": [
                  543613
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "currency": {
            "type": "string",
            "const": "THB",
            "description": "Always THB."
          },
          "paid_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 time (UTC).",
                "examples": [
                  "2026-10-07T03:15:00.000Z"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "bill_id",
          "property_id",
          "room_id",
          "status",
          "method",
          "amount",
          "amount_paid",
          "currency",
          "paid_at",
          "expires_at",
          "created_at"
        ],
        "description": "A tenant's PromptPay QR payment for a bill, confirmed by the payment provider. Read-only."
      },
      "PaymentLink": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "payment_link"
          },
          "bill_id": {
            "type": "string",
            "pattern": "^bill_[a-z0-9]{20,40}$",
            "description": "ID of the bill.",
            "examples": [
              "bill_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "url": {
            "type": "string",
            "description": "Send this to the tenant: it opens a PromptPay QR for the bill's current amount.",
            "examples": [
              "https://payment.doolae.online/b/7KQ2M9XWTRB4H6PZN3CA"
            ]
          }
        },
        "required": [
          "object",
          "bill_id",
          "url"
        ]
      },
      "Notification": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "notification"
          },
          "channel": {
            "type": "string",
            "const": "line"
          },
          "type": {
            "type": "string",
            "enum": [
              "new_bill",
              "payment_reminder",
              "overdue",
              "payment_received",
              "message"
            ]
          },
          "tenant_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^ten_[a-z0-9]{20,40}$",
                "description": "ID of the tenant.",
                "examples": [
                  "ten_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "bill_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^bill_[a-z0-9]{20,40}$",
                "description": "ID of the bill.",
                "examples": [
                  "bill_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "failed"
            ],
            "description": "queued: waiting for its first try or a retry. failed: DOOLAE stopped retrying."
          },
          "text": {
            "type": "string",
            "description": "The message as the tenant sees it."
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "error": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "enum": [
                      "tenant_not_on_line",
                      "notifications_disabled",
                      "not_a_tenant",
                      "feature_not_available",
                      "line_unavailable",
                      "not_sent_in_time",
                      "delivery_failed"
                    ]
                  },
                  "message": {
                    "type": "string"
                  }
                },
                "required": [
                  "code",
                  "message"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "sent_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 time (UTC).",
                "examples": [
                  "2026-10-07T03:15:00.000Z"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "channel",
          "type",
          "tenant_id",
          "bill_id",
          "status",
          "text",
          "attempts",
          "error",
          "sent_at",
          "created_at"
        ],
        "description": "A message to a tenant on DOOLAE LINE. DOOLAE queues and retries it; it is never delivered twice."
      },
      "NotificationCreate": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "new_bill",
              "payment_reminder",
              "overdue"
            ]
          },
          "bill_id": {
            "type": "string",
            "pattern": "^bill_[a-z0-9]{20,40}$",
            "description": "ID of the bill.",
            "examples": [
              "bill_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          }
        },
        "required": [
          "type",
          "bill_id"
        ],
        "additionalProperties": false,
        "description": "Sends a bill notice to the bill's tenant (with a payment QR when the landlord has QR payments on)."
      },
      "MessageCreate": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "examples": [
              "Water will be off on Saturday 10:00–12:00 for maintenance."
            ]
          },
          "tenant_id": {
            "type": "string",
            "pattern": "^ten_[a-z0-9]{20,40}$",
            "description": "ID of the tenant.",
            "examples": [
              "ten_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "room_id": {
            "type": "string",
            "pattern": "^room_[a-z0-9]{20,40}$",
            "description": "ID of the room.",
            "examples": [
              "room_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "property_id": {
            "type": "string",
            "pattern": "^prop_[a-z0-9]{20,40}$",
            "description": "ID of the property.",
            "examples": [
              "prop_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "description": "A text message to one tenant, the tenant of a room, or every tenant of a property."
      },
      "Event": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "event"
          },
          "type": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "property.created",
                  "property.updated",
                  "property.deleted",
                  "room.created",
                  "room.updated",
                  "room.deleted",
                  "tenant.created",
                  "tenant.updated",
                  "tenant.deleted",
                  "tenant.line_registered",
                  "meter.reading.created",
                  "meter.reading.updated",
                  "bill.created",
                  "bill.updated",
                  "bill.overdue",
                  "bill.deleted",
                  "payment.pending",
                  "payment.paid",
                  "payment.failed",
                  "notification.sent",
                  "notification.failed"
                ]
              },
              {
                "type": "string",
                "const": "webhook.test"
              }
            ]
          },
          "api_version": {
            "type": "string",
            "const": "v1"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "account_id": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^acct_[a-z0-9]{20,40}$",
                "description": "ID of the account.",
                "examples": [
                  "acct_cm2x8k1qz0001a8b3c4d5e6f7"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "data": {
            "type": "object",
            "properties": {
              "object": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {},
                "description": "The object as it was when the event was sent (same shape as the API returns it)."
              }
            },
            "required": [
              "object"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "api_version",
          "mode",
          "account_id",
          "created_at",
          "data"
        ],
        "description": "Something that happened in a connected account. Also the body of every webhook delivery."
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "webhook_endpoint"
          },
          "url": {
            "type": "string",
            "examples": [
              "https://example.com/webhooks/doolae"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "property.created",
                    "property.updated",
                    "property.deleted",
                    "room.created",
                    "room.updated",
                    "room.deleted",
                    "tenant.created",
                    "tenant.updated",
                    "tenant.deleted",
                    "tenant.line_registered",
                    "meter.reading.created",
                    "meter.reading.updated",
                    "bill.created",
                    "bill.updated",
                    "bill.overdue",
                    "bill.deleted",
                    "payment.pending",
                    "payment.paid",
                    "payment.failed",
                    "notification.sent",
                    "notification.failed"
                  ]
                },
                {
                  "type": "string",
                  "const": "*"
                }
              ]
            },
            "description": "Event types delivered here; [\"*\"] = all."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "api_version": {
            "type": "string",
            "const": "v1"
          },
          "status": {
            "type": "string",
            "enum": [
              "enabled",
              "disabled",
              "auto_disabled"
            ],
            "description": "auto_disabled: deliveries kept failing for 3 days; re-enable after fixing the endpoint."
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "secret": {
            "description": "Signing secret (whsec_…). Only returned when the endpoint is created or its secret is rotated.",
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 time (UTC).",
            "examples": [
              "2026-10-07T03:15:00.000Z"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "description",
          "events",
          "mode",
          "api_version",
          "status",
          "disabled_reason",
          "created_at",
          "updated_at"
        ]
      },
      "WebhookEndpointCreate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2000,
            "description": "HTTPS URL on the public internet."
          },
          "events": {
            "minItems": 1,
            "maxItems": 21,
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "property.created",
                    "property.updated",
                    "property.deleted",
                    "room.created",
                    "room.updated",
                    "room.deleted",
                    "tenant.created",
                    "tenant.updated",
                    "tenant.deleted",
                    "tenant.line_registered",
                    "meter.reading.created",
                    "meter.reading.updated",
                    "bill.created",
                    "bill.updated",
                    "bill.overdue",
                    "bill.deleted",
                    "payment.pending",
                    "payment.paid",
                    "payment.failed",
                    "notification.sent",
                    "notification.failed"
                  ]
                },
                {
                  "type": "string",
                  "const": "*"
                }
              ]
            }
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "url",
          "events"
        ],
        "additionalProperties": false,
        "description": "Created in the key's mode: test keys create test endpoints."
      },
      "WebhookEndpointUpdate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2000
          },
          "events": {
            "minItems": 1,
            "maxItems": 21,
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "property.created",
                    "property.updated",
                    "property.deleted",
                    "room.created",
                    "room.updated",
                    "room.deleted",
                    "tenant.created",
                    "tenant.updated",
                    "tenant.deleted",
                    "tenant.line_registered",
                    "meter.reading.created",
                    "meter.reading.updated",
                    "bill.created",
                    "bill.updated",
                    "bill.overdue",
                    "bill.deleted",
                    "payment.pending",
                    "payment.paid",
                    "payment.failed",
                    "notification.sent",
                    "notification.failed"
                  ]
                },
                {
                  "type": "string",
                  "const": "*"
                }
              ]
            }
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "enabled": {
            "description": "false pauses deliveries; true re-enables (also after auto-disable).",
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "TestPaymentCreate": {
        "type": "object",
        "properties": {
          "bill_id": {
            "type": "string",
            "pattern": "^bill_[a-z0-9]{20,40}$",
            "description": "ID of the sandbox bill.",
            "examples": [
              "bill_cm2x8k1qz0001a8b3c4d5e6f7"
            ]
          },
          "outcome": {
            "default": "paid",
            "type": "string",
            "enum": [
              "paid",
              "expired",
              "canceled"
            ]
          }
        },
        "required": [
          "bill_id"
        ],
        "additionalProperties": false,
        "description": "Test mode only: simulates a tenant's QR payment for a sandbox bill (sends payment.pending, then payment.paid or payment.failed)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is not valid (unknown or invalid parameter, invalid JSON, cursor…).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_parameter",
                "message": "A parameter has an invalid value.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#invalid_parameter"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, revoked or expired API key.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_api_key",
                "message": "The API key is invalid.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#invalid_api_key"
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Live mode only: an invoice for this developer account is overdue.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "payment_required",
                "message": "An invoice for this developer account is overdue. Pay it in the developer portal to continue using live mode.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#payment_required"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key lacks the required scope, the account isn't connected, or the landlord's plan doesn't allow it.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "insufficient_scope",
                "message": "This API key does not have the scope this endpoint requires.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#insufficient_scope"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource in this account (resources of other accounts look exactly like missing ones).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "resource_not_found",
                "message": "The requested resource was not found.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#resource_not_found"
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "conflict",
                "message": "The request conflicts with the current state of the resource.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#conflict"
              }
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "The request body is larger than 1 MB.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "request_too_large",
                "message": "The request body is too large.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#request_too_large"
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "The request body must be application/json.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "unsupported_media_type",
                "message": "Send the request body as application/json.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#unsupported_media_type"
              }
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The request is well-formed but not allowed (e.g. a meter value lower than last month's).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_reading",
                "message": "The meter reading is not valid.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#invalid_reading"
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit or monthly quota reached. Retry after the Retry-After seconds.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Too many requests. Slow down and retry after the time in the Retry-After header.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#rate_limited"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "An unexpected error on DOOLAE's side.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "Something went wrong on our side. Use the request ID if you contact support.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#internal_error"
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Temporarily busy or a request took too long. Retry with backoff (writes: with the same Idempotency-Key).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "service_unavailable",
                "message": "The service is busy. Retry after the time in the Retry-After header.",
                "request_id": "req_3f2a9c1d7b8e4f60a1b2c3d4e5f60718",
                "doc_url": "https://developers.doolae.online/docs/guides/errors#service_unavailable"
              }
            }
          }
        }
      }
    },
    "headers": {
      "RequestId": {
        "description": "Unique ID of this request (req_…). Quote it when contacting support.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requests you can make at once (burst size of your plan).",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current burst.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Unix time (seconds) when the burst is fully refilled.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "Billable requests included in your plan this month (live mode).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Included requests left this month (live mode).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaReset": {
        "description": "Unix time (seconds) when the monthly quota resets (00:00 on the 1st, Asia/Bangkok).",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplayed": {
        "description": "true when this is the stored response of an earlier request with the same Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Up to 255 printable ASCII characters (a UUID is ideal). Repeating a request with the same key and body returns the stored response instead of doing it twice; keys are kept 24 hours per application and account.",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "DoolaeAccount": {
        "name": "DOOLAE-Account",
        "in": "header",
        "required": false,
        "description": "The connected account (acct_…) to act on. Only needed for live keys that aren't bound to one account.",
        "schema": {
          "type": "string",
          "pattern": "^acct_[a-z0-9]{20,40}$"
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "Your secret API key: `dl_live_…` (live) or `dl_test_…` (test mode, sandbox data). Server-side only."
      }
    }
  }
}